[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "content": "---\nname: Bug report\ndescription: Create a report to help us improve Flux\nbody:\n- type: markdown\n  attributes:\n    value: |\n      ## Support\n      Find out more about your support options and getting help at: https://fluxcd.io/support/\n- type: textarea\n  validations:\n    required: true\n  attributes:\n    label: Describe the bug\n    description: A clear description of what the bug is.\n- type: textarea\n  validations:\n    required: true\n  attributes:\n    label: Steps to reproduce\n    description: |\n      Steps to reproduce the problem.\n    placeholder: |\n      For example:\n      1. Install Flux with the additional image automation controllers\n      2. Run command '...'\n      3. See error\n- type: textarea\n  validations:\n    required: true\n  attributes:\n    label: Expected behavior\n    description: A brief description of what you expected to happen.\n- type: textarea\n  attributes:\n    label: Screenshots and recordings\n    description: |\n      If applicable, add screenshots to help explain your problem. You can also record an asciinema session: https://asciinema.org/\n- type: input\n  validations:\n    required: true\n  attributes:\n    label: OS / Distro\n    description: The OS / distro you are executing `flux` on. If not applicable, write `N/A`.\n    placeholder: e.g. Windows 10, Ubuntu 20.04, Arch Linux, macOS 10.15...\n- type: input\n  validations:\n    required: true\n  attributes:\n    label: Flux version\n    description: Run `flux version --client`. If not applicable, write `N/A`.\n    placeholder: e.g. v0.20.1\n- type: textarea\n  validations:\n    required: true\n  attributes:\n    label: Flux check\n    description: Run `flux check`. If not applicable, write `N/A`.\n    placeholder: |\n      For example:\n      ► checking prerequisites\n      ✔ Kubernetes 1.21.1 >=1.19.0-0\n      ► checking controllers\n      ✔ all checks passed\n- type: input\n  attributes:\n    label: Git provider\n    description: If applicable, add the Git provider you are having problems with, e.g. GitHub (Enterprise), GitLab, etc.\n- type: input\n  attributes:\n    label: Container Registry provider\n    description: If applicable, add the Container Registry provider you are having problems with, e.g. DockerHub, GitHub Packages, Quay.io, etc.\n- type: textarea\n  attributes:\n    label: Additional context\n    description: Add any other context about the problem here. This can be logs (e.g. output from `flux logs`), environment specific caveats, etc.\n- type: checkboxes\n  id: terms\n  attributes:\n    label: Code of Conduct\n    description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/fluxcd/.github/blob/main/CODE_OF_CONDUCT.md)\n    options:\n    - label: I agree to follow this project's Code of Conduct\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: Ask a question\n    url: https://github.com/fluxcd/flux2/discussions\n    about: Please ask and answer questions here.\n"
  },
  {
    "path": ".github/aur/flux-bin/.SRCINFO.template",
    "content": "pkgbase = flux-bin\n\tpkgdesc = Open and extensible continuous delivery solution for Kubernetes\n\tpkgver = ${PKGVER}\n\tpkgrel = ${PKGREL}\n\turl = https://fluxcd.io/\n\tarch = x86_64\n\tarch = armv7h\n\tarch = aarch64\n\tlicense = APACHE\n\toptdepends = bash-completion: auto-completion for flux in Bash\n\toptdepends = zsh-completions: auto-completion for flux in ZSH\n\tsource_x86_64 = flux-bin-${PKGVER}_linux_amd64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_amd64.tar.gz\n\tsha256sums_x86_64 = ${SHA256SUM_AMD64}\n\tsource_armv7h = flux-bin-${PKGVER}_linux_arm.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm.tar.gz\n\tsha256sums_armv7h = ${SHA256SUM_ARM}\n\tsource_aarch64 = flux-bin-${PKGVER}_linux_arm64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm64.tar.gz\n\tsha256sums_aarch64 = ${SHA256SUM_ARM64}\n\npkgname = flux-bin\n"
  },
  {
    "path": ".github/aur/flux-bin/.gitignore",
    "content": ".pkg\n"
  },
  {
    "path": ".github/aur/flux-bin/PKGBUILD.template",
    "content": "# Maintainer: Aurel Canciu <aurelcanciu@gmail.com>\n# Maintainer: Hidde Beydals <hello@hidde.co>\n\npkgname=flux-bin\npkgver=${PKGVER}\npkgrel=${PKGREL}\n_srcname=flux\n_srcver=${VERSION}\npkgdesc=\"Open and extensible continuous delivery solution for Kubernetes\"\nurl=\"https://fluxcd.io/\"\narch=(\"x86_64\" \"armv7h\" \"aarch64\")\nlicense=(\"APACHE\")\noptdepends=('bash-completion: auto-completion for flux in Bash'\n            'zsh-completions: auto-completion for flux in ZSH')\nsource_x86_64=(\n  \"${pkgname}-${pkgver}_linux_amd64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_amd64.tar.gz\"\n)\nsource_armv7h=(\n  \"${pkgname}-${pkgver}_linux_arm.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm.tar.gz\"\n)\nsource_aarch64=(\n  \"${pkgname}-${pkgver}_linux_arm64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm64.tar.gz\"\n)\nsha256sums_x86_64=(\n  ${SHA256SUM_AMD64}\n)\nsha256sums_armv7h=(\n  ${SHA256SUM_ARM}\n)\nsha256sums_aarch64=(\n  ${SHA256SUM_ARM64}\n)\n\npackage() {\n\tinstall -Dm755 ${_srcname} \"${pkgdir}/usr/bin/${_srcname}\"\n\n    \"${pkgdir}/usr/bin/${_srcname}\" completion bash | install -Dm644 /dev/stdin \"${pkgdir}/usr/share/bash-completion/completions/${_srcname}\"\n    \"${pkgdir}/usr/bin/${_srcname}\" completion fish | install -Dm644 /dev/stdin \"${pkgdir}/usr/share/fish/vendor_completions.d/${_srcname}.fish\"\n    \"${pkgdir}/usr/bin/${_srcname}\" completion zsh | install -Dm644 /dev/stdin \"${pkgdir}/usr/share/zsh/site-functions/_${_srcname}\"\n}\n"
  },
  {
    "path": ".github/aur/flux-bin/publish.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nWD=$(cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd)\nPKGNAME=$(basename $WD)\nROOT=${WD%/.github/aur/$PKGNAME}\n\nLOCKFILE=/tmp/aur-$PKGNAME.lock\nexec 100>$LOCKFILE || exit 0\nflock -n 100 || exit 0\ntrap \"rm -f $LOCKFILE\" EXIT\n\nexport VERSION=$1\necho \"Publishing to AUR as version ${VERSION}\"\n\ncd $WD\n\nexport GIT_SSH_COMMAND=\"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no\"\n\neval $(ssh-agent -s)\nssh-add <(echo \"$AUR_BOT_SSH_PRIVATE_KEY\")\n\nGITDIR=$(mktemp -d /tmp/aur-$PKGNAME-XXX)\ntrap \"rm -rf $GITDIR\" EXIT\ngit clone aur@aur.archlinux.org:$PKGNAME $GITDIR 2>&1\n\nCURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }')\nCURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }')\n\n# Transform pre-release to AUR compatible version format\nexport PKGVER=${VERSION/-/}\n\nif [[ \"${CURRENT_PKGVER}\" == \"${PKGVER}\" ]]; then\n    export PKGREL=$((CURRENT_PKGREL+1))\nelse\n    export PKGREL=1\nfi\n\nexport SHA256SUM_ARM=$(sha256sum ${ROOT}/dist/flux_${VERSION}_linux_arm.tar.gz | awk '{ print $1 }')\nexport SHA256SUM_ARM64=$(sha256sum ${ROOT}/dist/flux_${VERSION}_linux_arm64.tar.gz | awk '{ print $1 }')\nexport SHA256SUM_AMD64=$(sha256sum ${ROOT}/dist/flux_${VERSION}_linux_amd64.tar.gz | awk '{ print $1 }')\n\nenvsubst '$VERSION $PKGVER $PKGREL $SHA256SUM_AMD64 $SHA256SUM_ARM $SHA256SUM_ARM64' < .SRCINFO.template > $GITDIR/.SRCINFO\nenvsubst '$VERSION $PKGVER $PKGREL $SHA256SUM_AMD64 $SHA256SUM_ARM $SHA256SUM_ARM64' < PKGBUILD.template > $GITDIR/PKGBUILD\n\ncd $GITDIR\ngit config user.name \"fluxcdbot\"\ngit config user.email \"fluxcdbot@users.noreply.github.com\"\ngit add -A\nif [ -z \"$(git status --porcelain)\" ]; then\n  echo \"No changes.\"\nelse\n  git commit -m \"Updated to version v${PKGVER} release ${PKGREL}\"\n  git push origin master\nfi\n"
  },
  {
    "path": ".github/aur/flux-go/.SRCINFO.template",
    "content": "pkgbase = flux-go\n\tpkgdesc = Open and extensible continuous delivery solution for Kubernetes\n\tpkgver = ${PKGVER}\n\tpkgrel = ${PKGREL}\n\turl = https://fluxcd.io/\n\tarch = x86_64\n\tarch = armv7h\n\tarch = aarch64\n\tlicense = APACHE\n\tmakedepends = go\n\tdepends = glibc\n\tprovides = flux-bin\n\tconflicts = flux-bin\n  replaces = flux-cli\n\tsource = flux-go-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/archive/v${VERSION}.tar.gz\n\npkgname = flux-go\n"
  },
  {
    "path": ".github/aur/flux-go/.gitignore",
    "content": ".pkg\n"
  },
  {
    "path": ".github/aur/flux-go/PKGBUILD.template",
    "content": "# Maintainer: Aurel Canciu <aurelcanciu@gmail.com>\n# Maintainer: Hidde Beydals <hello@hidde.co>\n\npkgname=flux-go\npkgver=${PKGVER}\npkgrel=${PKGREL}\n_srcname=flux\n_srcver=${VERSION}\npkgdesc=\"Open and extensible continuous delivery solution for Kubernetes\"\nurl=\"https://fluxcd.io/\"\narch=(\"x86_64\" \"armv7h\" \"aarch64\")\nlicense=(\"APACHE\")\nprovides=(\"flux-bin\")\nconflicts=(\"flux-bin\")\nreplaces=(\"flux-cli\")\ndepends=(\"glibc\")\nmakedepends=('go>=1.20', 'kustomize>=5.0')\noptdepends=('bash-completion: auto-completion for flux in Bash',\n'zsh-completions: auto-completion for flux in ZSH')\nsource=(\n  \"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/archive/v${_srcver}.tar.gz\"\n)\nsha256sums=(\n  ${SHA256SUM}\n)\n\nbuild() {\n  cd \"flux2-${_srcver}\"\n  export CGO_LDFLAGS=\"$LDFLAGS\"\n  export CGO_CFLAGS=\"$CFLAGS\"\n  export CGO_CXXFLAGS=\"$CXXFLAGS\"\n  export CGO_CPPFLAGS=\"$CPPFLAGS\"\n  export GOFLAGS=\"-buildmode=pie -trimpath -mod=readonly -modcacherw\"\n  make cmd/flux/.manifests.done\n  go build -ldflags \"-linkmode=external -X main.VERSION=${_srcver}\" -o ${_srcname} ./cmd/flux\n}\n\ncheck() {\n  cd \"flux2-${_srcver}\"\n  case $CARCH in\n    aarch64)\n      export ENVTEST_ARCH=arm64\n      ;;\n    armv7h)\n      export ENVTEST_ARCH=arm\n      ;;\n  esac\n  make test\n}\n\npackage() {\n  cd \"flux2-${_srcver}\"\n  install -Dm755 ${_srcname} \"${pkgdir}/usr/bin/${_srcname}\"\n  install -Dm644 LICENSE \"${pkgdir}/usr/share/licenses/${pkgname}/LICENSE\"\n\n  \"${pkgdir}/usr/bin/${_srcname}\" completion bash | install -Dm644 /dev/stdin \"${pkgdir}/usr/share/bash-completion/completions/${_srcname}\"\n  \"${pkgdir}/usr/bin/${_srcname}\" completion fish | install -Dm644 /dev/stdin \"${pkgdir}/usr/share/fish/vendor_completions.d/${_srcname}.fish\"\n  \"${pkgdir}/usr/bin/${_srcname}\" completion zsh | install -Dm644 /dev/stdin \"${pkgdir}/usr/share/zsh/site-functions/_${_srcname}\"\n}\n"
  },
  {
    "path": ".github/aur/flux-go/publish.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nWD=$(cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd)\nPKGNAME=$(basename $WD)\nROOT=${WD%/.github/aur/$PKGNAME}\n\nLOCKFILE=/tmp/aur-$PKGNAME.lock\nexec 100>$LOCKFILE || exit 0\nflock -n 100 || exit 0\ntrap \"rm -f $LOCKFILE\" EXIT\n\nexport VERSION=$1\necho \"Publishing to AUR as version ${VERSION}\"\n\ncd $WD\n\nexport GIT_SSH_COMMAND=\"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no\"\n\neval $(ssh-agent -s)\nssh-add <(echo \"$AUR_BOT_SSH_PRIVATE_KEY\")\n\nGITDIR=$(mktemp -d /tmp/aur-$PKGNAME-XXX)\ntrap \"rm -rf $GITDIR\" EXIT\ngit clone aur@aur.archlinux.org:$PKGNAME $GITDIR 2>&1\n\nCURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }')\nCURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }')\n\n# Transform pre-release to AUR compatible version format\nexport PKGVER=${VERSION/-/}\n\nif [[ \"${CURRENT_PKGVER}\" == \"${PKGVER}\" ]]; then\n    export PKGREL=$((CURRENT_PKGREL+1))\nelse\n    export PKGREL=1\nfi\n\nexport SHA256SUM=$(curl -sL https://github.com/fluxcd/flux2/archive/v${VERSION}.tar.gz | sha256sum | awk '{ print $1 }')\n\nenvsubst '$VERSION $PKGVER $PKGREL $SHA256SUM' < .SRCINFO.template > $GITDIR/.SRCINFO\nenvsubst '$VERSION $PKGVER $PKGREL $SHA256SUM' < PKGBUILD.template > $GITDIR/PKGBUILD\n\ncd $GITDIR\ngit config user.name \"fluxcdbot\"\ngit config user.email \"fluxcdbot@users.noreply.github.com\"\ngit add -A\nif [ -z \"$(git status --porcelain)\" ]; then\n  echo \"No changes.\"\nelse\n  git commit -m \"Updated to version v${PKGVER} release ${PKGREL}\"\n  git push origin master\nfi\n"
  },
  {
    "path": ".github/aur/flux-scm/.SRCINFO.template",
    "content": "pkgbase = flux-scm\n\tpkgdesc = Open and extensible continuous delivery solution for Kubernetes\n\tpkgver = ${PKGVER}\n\tpkgrel = ${PKGREL}\n\turl = https://fluxcd.io/\n\tarch = x86_64\n\tarch = armv7h\n\tarch = aarch64\n\tlicense = APACHE\n\tmakedepends = go\n\tdepends = glibc\n\tprovides = flux-bin\n\tconflicts = flux-bin\n\tsource = git+https://github.com/fluxcd/flux2.git\n\tmd5sums = SKIP\n\npkgname = flux-scm\n"
  },
  {
    "path": ".github/aur/flux-scm/.gitignore",
    "content": ".pkg\n"
  },
  {
    "path": ".github/aur/flux-scm/PKGBUILD.template",
    "content": "# Maintainer: Aurel Canciu <aurelcanciu@gmail.com>\n# Maintainer: Hidde Beydals <hello@hidde.co>\n\npkgname=flux-scm\npkgver=${PKGVER}\npkgrel=${PKGREL}\n_srcname=flux\npkgdesc=\"Open and extensible continuous delivery solution for Kubernetes\"\nurl=\"https://fluxcd.io/\"\narch=(\"x86_64\" \"armv7h\" \"aarch64\")\nlicense=(\"APACHE\")\nprovides=(\"flux-bin\")\nconflicts=(\"flux-bin\")\ndepends=(\"glibc\")\nmakedepends=('go>=1.20', 'kustomize>=5.0', 'git')\noptdepends=('bash-completion: auto-completion for flux in Bash',\n'zsh-completions: auto-completion for flux in ZSH')\nsource=(\n  \"git+https://github.com/fluxcd/flux2.git\"\n)\nmd5sums=('SKIP')\n\npkgver() {\n  cd \"flux2\"\n  printf \"r%s.%s\" \"$(git rev-list --count HEAD)\" \"$(git rev-parse --short HEAD)\"\n}\n\nbuild() {\n  cd \"flux2\"\n  export CGO_LDFLAGS=\"$LDFLAGS\"\n  export CGO_CFLAGS=\"$CFLAGS\"\n  export CGO_CXXFLAGS=\"$CXXFLAGS\"\n  export CGO_CPPFLAGS=\"$CPPFLAGS\"\n  export GOFLAGS=\"-buildmode=pie -trimpath -mod=readonly -modcacherw\"\n  make cmd/flux/.manifests.done\n  go build -ldflags \"-linkmode=external -X main.VERSION=${pkgver}\" -o ${_srcname} ./cmd/flux\n}\n\ncheck() {\n  cd \"flux2\"\n  case $CARCH in\n    aarch64)\n      export ENVTEST_ARCH=arm64\n      ;;\n    armv7h)\n      export ENVTEST_ARCH=arm\n      ;;\n  esac\n  make test\n}\n\npackage() {\n  cd \"flux2\"\n  install -Dm755 ${_srcname} \"${pkgdir}/usr/bin/${_srcname}\"\n  install -Dm644 LICENSE \"${pkgdir}/usr/share/licenses/${pkgname}/LICENSE\"\n\n  \"${pkgdir}/usr/bin/${_srcname}\" completion bash | install -Dm644 /dev/stdin \"${pkgdir}/usr/share/bash-completion/completions/${_srcname}\"\n  \"${pkgdir}/usr/bin/${_srcname}\" completion fish | install -Dm644 /dev/stdin \"${pkgdir}/usr/share/fish/vendor_completions.d/${_srcname}.fish\"\n  \"${pkgdir}/usr/bin/${_srcname}\" completion zsh | install -Dm644 /dev/stdin \"${pkgdir}/usr/share/zsh/site-functions/_${_srcname}\"\n}\n"
  },
  {
    "path": ".github/aur/flux-scm/publish.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nWD=$(cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd)\nPKGNAME=$(basename $WD)\nROOT=${WD%/.github/aur/$PKGNAME}\n\nLOCKFILE=/tmp/aur-$PKGNAME.lock\nexec 100>$LOCKFILE || exit 0\nflock -n 100 || exit 0\ntrap \"rm -f $LOCKFILE\" EXIT\n\nexport VERSION=$1\necho \"Publishing to AUR as version ${VERSION}\"\n\ncd $WD\n\nexport GIT_SSH_COMMAND=\"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no\"\n\neval $(ssh-agent -s)\nssh-add <(echo \"$AUR_BOT_SSH_PRIVATE_KEY\")\n\nGITDIR=$(mktemp -d /tmp/aur-$PKGNAME-XXX)\ntrap \"rm -rf $GITDIR\" EXIT\ngit clone aur@aur.archlinux.org:$PKGNAME $GITDIR 2>&1\n\nCURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }')\nCURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }')\n\n# Transform pre-release to AUR compatible version format\nexport PKGVER=${VERSION/-/}\n\nif [[ \"${CURRENT_PKGVER}\" == \"${PKGVER}\" ]]; then\n    export PKGREL=$((CURRENT_PKGREL+1))\nelse\n    export PKGREL=1\nfi\n\nenvsubst '$PKGVER $PKGREL' < .SRCINFO.template > $GITDIR/.SRCINFO\nenvsubst '$PKGVER $PKGREL' < PKGBUILD.template > $GITDIR/PKGBUILD\n\ncd $GITDIR\ngit config user.name \"fluxcdbot\"\ngit config user.email \"fluxcdbot@users.noreply.github.com\"\ngit add -A\nif [ -z \"$(git status --porcelain)\" ]; then\n  echo \"No changes.\"\nelse\n  git commit -m \"Updated to version v${PKGVER} release ${PKGREL}\"\n  git push origin master\nfi\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\n\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    labels: [\"area/ci\", \"dependencies\"]\n    groups:\n      # Group all updates together, so that they are all applied in a single PR.\n      # xref: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups\n      ci:\n        patterns:\n          - \"*\"\n    schedule:\n      interval: \"monthly\"\n"
  },
  {
    "path": ".github/kind/config.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n  - role: control-plane\n  - role: worker\n  - role: worker\nnetworking:\n  disableDefaultCNI: true   # disable kindnet\n  podSubnet: 192.168.0.0/16 # set to Calico's default subnet\n"
  },
  {
    "path": ".github/labels.yaml",
    "content": "# Configuration file to declaratively configure labels\n# Ref: https://github.com/EndBug/label-sync#Config-files\n\n- name: area/bootstrap\n  description: Bootstrap related issues and pull requests\n  color: '#86efc9'\n- name: area/install\n  description: Install and uninstall related issues and pull requests\n  color: '#86efc9'\n- name: area/diff\n  description: Diff related issues and pull requests\n  color: '#BA4192'\n- name: area/bucket\n  description: Bucket related issues and pull requests\n  color: '#00b140'\n- name: area/git\n  description: Git related issues and pull requests\n  color: '#863faf'\n- name: area/oci\n  description: OCI related issues and pull requests\n  color: '#c739ff'\n- name: area/kustomization\n  description: Kustomization related issues and pull requests\n  color: '#00e54d'\n- name: area/helm\n  description: Helm related issues and pull requests\n  color: '#1673b6'\n- name: area/image-automation\n  description: Automated image updates related issues and pull requests\n  color: '#c5def5'\n- name: area/monitoring\n  description: Monitoring related issues and pull requests\n  color: '#dd75ae'\n- name: area/multi-tenancy\n  description: Multi-tenancy related issues and pull requests\n  color: '#72CDBD'\n- name: area/notification\n  description: Notification API related issues and pull requests\n  color: '#434ec1'\n- name: area/source\n  description: Source API related issues and pull requests\n  color: '#863faf'\n- name: area/rfc\n  description: Feature request proposals in the RFC format\n  color: '#D621C3'\n  aliases: ['area/RFC']\n- name: backport:release/v2.6.x\n  description: To be backported to release/v2.6.x\n  color: '#ffd700'\n- name: backport:release/v2.7.x\n  description: To be backported to release/v2.7.x\n  color: '#ffd700'\n- name: backport:release/v2.8.x\n  description: To be backported to release/v2.8.x\n  color: '#ffd700'\n"
  },
  {
    "path": ".github/runners/README.md",
    "content": "# Flux ARM64 GitHub runners\n\nThe Flux ARM64 end-to-end tests run on Equinix Metal instances provisioned with Docker and GitHub self-hosted runners.\n\n## Current instances\n\n| Repository                  | Runner           | Instance       | Location      |\n|-----------------------------|------------------|----------------|---------------|\n| flux2                       | equinix-arm-dc-1 | flux-arm-dc-01 | Washington DC |\n| flux2                       | equinix-arm-dc-2 | flux-arm-dc-01 | Washington DC |\n| flux2                       | equinix-arm-da-1 | flux-arm-da-01 | Dallas        |\n| flux2                       | equinix-arm-da-2 | flux-arm-da-01 | Dallas        |\n| flux-benchmark              | equinix-arm-dc-1 | flux-arm-dc-01 | Washington DC |\n| flux-benchmark              | equinix-arm-da-1 | flux-arm-da-01 | Dallas        |\n| source-controller           | equinix-arm-dc-1 | flux-arm-dc-01 | Washington DC |\n| source-controller           | equinix-arm-da-1 | flux-arm-da-01 | Dallas        |\n| image-automation-controller | equinix-arm-dc-1 | flux-arm-dc-01 | Washington DC |\n| image-automation-controller | equinix-arm-da-1 | flux-arm-da-01 | Dallas        |\n\nInstance spec:\n- Ampere Altra Q80-30 80-core processor @ 2.8GHz\n- 2 x 960GB NVME\n- 256GB RAM\n- 2 x 25Gbps\n\n## Instance setup\n\nIn order to add a new runner to the GitHub Actions pool,\nfirst create a server on Equinix with the following configuration:\n- Type: `c3.large.arm64`\n- OS: `Ubuntu 22.04 LTS`\n\n### Install prerequisites\n\n- SSH into a newly created instance\n```shell\nssh root@<instance-public-IP>\n``` \n\n- Create the ubuntu user\n```shell\nadduser ubuntu\nusermod -aG sudo ubuntu\nsu - ubuntu\n```\n\n- Create the prerequisites dir\n```shell\nmkdir -p prereq && cd prereq\n```\n\n- Download the prerequisites script\n```shell\ncurl -sL https://raw.githubusercontent.com/fluxcd/flux2/main/.github/runners/prereq.sh > prereq.sh \\\n  && chmod +x ./prereq.sh\n```\n\n- Install the prerequisites\n```shell\nsudo ./prereq.sh\n```\n\n### Install runners\n\n- Retrieve the GitHub runner token from the repository [settings page](https://github.com/fluxcd/flux2/settings/actions/runners/new?arch=arm64&os=linux)\n\n- Create two directories `flux2-01`, `flux2-02`\n\n- In each dir run:\n```shell\ncurl -sL https://raw.githubusercontent.com/fluxcd/flux2/main/.github/runners/runner-setup.sh > runner-setup.sh \\\n  && chmod +x ./runner-setup.sh\n\n./runner-setup.sh equinix-arm-<NUMBER> <TOKEN> <REPO>\n```\n\n- Reboot the instance\n```shell\nsudo reboot\n```\n\n- Navigate to the GitHub repository [runners page](https://github.com/fluxcd/flux2/settings/actions/runners) and check the runner status\n"
  },
  {
    "path": ".github/runners/prereq.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2021 The Flux authors. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# This script installs the prerequisites for running Flux end-to-end tests with Docker and GitHub self-hosted runners.\n\nset -eu\n\nKIND_VERSION=0.22.0\nKUBECTL_VERSION=1.29.0\nKUSTOMIZE_VERSION=5.3.0\nHELM_VERSION=3.14.1\nGITHUB_RUNNER_VERSION=2.313.0\nPACKAGES=\"apt-transport-https ca-certificates software-properties-common build-essential libssl-dev gnupg lsb-release jq pkg-config\"\n\n# install prerequisites\napt-get update \\\n  && apt-get install -y -q ${PACKAGES} \\\n  && apt-get clean \\\n  && rm -rf /var/lib/apt/lists/*\n\n# fix Kubernetes DNS resolution\nrm /etc/resolv.conf\ncat \"/run/systemd/resolve/stub-resolv.conf\" | sed '/search/d' > /etc/resolv.conf\n\n# install docker\ncurl -fsSL https://get.docker.com -o get-docker.sh \\\n  && chmod +x get-docker.sh\n./get-docker.sh\nsystemctl enable docker.service\nsystemctl enable containerd.service\nusermod -aG docker ubuntu\n\n# install kind\ncurl -Lo ./kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-arm64\ninstall -o root -g root -m 0755 kind /usr/local/bin/kind\n\n# install kubectl\ncurl -LO \"https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/arm64/kubectl\"\ninstall -o root -g root -m 0755 kubectl /usr/local/bin/kubectl\n\n# install kustomize\ncurl -Lo ./kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_linux_arm64.tar.gz \\\n  && tar -zxvf kustomize.tar.gz \\\n  && rm kustomize.tar.gz\ninstall -o root -g root -m 0755 kustomize /usr/local/bin/kustomize\n\n# install helm\ncurl -Lo ./helm.tar.gz https://get.helm.sh/helm-v${HELM_VERSION}-linux-arm64.tar.gz \\\n  && tar -zxvf helm.tar.gz \\\n  && rm helm.tar.gz\ninstall -o root -g root -m 0755 linux-arm64/helm /usr/local/bin/helm\n\n# download runner\ncurl -o actions-runner-linux-arm64.tar.gz -L https://github.com/actions/runner/releases/download/v${GITHUB_RUNNER_VERSION}/actions-runner-linux-arm64-${GITHUB_RUNNER_VERSION}.tar.gz \\\n  && tar xzf actions-runner-linux-arm64.tar.gz \\\n  && rm actions-runner-linux-arm64.tar.gz\n\n# install runner dependencies\n./bin/installdependencies.sh\n"
  },
  {
    "path": ".github/runners/runner-setup.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2021 The Flux authors. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# This script installs a GitHub self-hosted ARM64 runner for running Flux end-to-end tests.\n\nset -eu\n\nRUNNER_NAME=$1\nREPOSITORY_TOKEN=$2\nREPOSITORY_URL=${3:-https://github.com/fluxcd/flux2}\n\nGITHUB_RUNNER_VERSION=2.313.0\n\n# download runner\ncurl -o actions-runner-linux-arm64.tar.gz -L https://github.com/actions/runner/releases/download/v${GITHUB_RUNNER_VERSION}/actions-runner-linux-arm64-${GITHUB_RUNNER_VERSION}.tar.gz \\\n  && tar xzf actions-runner-linux-arm64.tar.gz \\\n  && rm actions-runner-linux-arm64.tar.gz\n\n# register runner with GitHub\n./config.sh --unattended --url ${REPOSITORY_URL} --token ${REPOSITORY_TOKEN} --name ${RUNNER_NAME}\n\n# start runner\nsudo ./svc.sh install\nsudo ./svc.sh start\n"
  },
  {
    "path": ".github/workflows/README.md",
    "content": "# Flux GitHub Workflows\n\n## End-to-end Testing\n\nThe e2e workflows run a series of tests to ensure that the Flux CLI and\nthe GitOps Toolkit controllers work well all together.\nThe tests are written in Go, Bash, Make and Terraform.\n\n| Workflow           | Jobs                 | Runner         | Role                                          |\n|--------------------|----------------------|----------------|-----------------------------------------------|\n| e2e.yaml           | e2e-amd64-kubernetes | GitHub Ubuntu  | integration testing with Kubernetes Kind<br/> |\n| e2e-arm64.yaml     | e2e-arm64-kubernetes | Equinix Ubuntu | integration testing with Kubernetes Kind<br/> |\n| e2e-bootstrap.yaml | e2e-boostrap-github  | GitHub Ubuntu  | integration testing with GitHub API<br/>      |\n| e2e-azure.yaml     | e2e-amd64-aks        | GitHub Ubuntu  | integration testing with Azure API<br/>       |\n| scan.yaml          | scan-fossa           | GitHub Ubuntu  | license scanning<br/>                         |\n| scan.yaml          | scan-snyk            | GitHub Ubuntu  | vulnerability scanning<br/>                   |\n| scan.yaml          | scan-codeql          | GitHub Ubuntu  | vulnerability scanning<br/>                   |\n\n## Components Update\n\nThe components update workflow scans the GitOps Toolkit controller repositories for new releases,\namd when it finds a new controller version, the workflow performs the following steps:\n- Updates the controller API package version in `go.mod`.\n- Patches the controller CRDs version in the `manifests/crds` overlay.\n- Patches the controller Deployment version in `manifests/bases` overlay.\n- Opens a Pull Request against the checked out branch.\n- Triggers the e2e test suite to run for the opened PR.\n\n\n| Workflow    | Jobs              | Runner        | Role                                                |\n|-------------|-------------------|---------------|-----------------------------------------------------|\n| update.yaml | update-components | GitHub Ubuntu | update the GitOps Toolkit APIs and controllers<br/> |\n\n## Release\n\nThe release workflow is triggered by a semver Git tag and performs the following steps:\n- Generates the Flux install manifests (YAML).\n- Generates the OpenAPI validation schemas for the GitOps Toolkit CRDs (JSON).\n- Generates a Software Bill of Materials (SPDX JSON).\n- Builds the Flux CLI binaries and the multi-arch container images.\n- Pushes the container images to GitHub Container Registry and DockerHub.\n- Signs the sbom, the binaries checksum and the container images with Cosign and GitHub OIDC.\n- Uploads the sbom, binaries, checksums and install manifests to GitHub Releases.\n- Pushes the install manifests as OCI artifacts to GitHub Container Registry and DockerHub.\n- Signs the OCI artifacts with Cosign and GitHub OIDC.\n\n| Workflow     | Jobs                   | Runner        | Role                                                 |\n|--------------|------------------------|---------------|------------------------------------------------------|\n| release.yaml | release-flux-cli       | GitHub Ubuntu | build, push and sign the CLI release artifacts<br/>  |\n| release.yaml | release-flux-manifests | GitHub Ubuntu | build, push and sign the Flux install manifests<br/> |\n"
  },
  {
    "path": ".github/workflows/action.yaml",
    "content": "name: test-gh-action\n\non:\n  pull_request:\n    paths:\n      - 'action/**'\n  push:\n    paths:\n      - 'action/**'\n    branches:\n      - 'main'\n      - 'release/**'\n\npermissions: read-all\n\njobs:\n  actions:\n    strategy:\n      fail-fast: false\n      matrix:\n        version: [ubuntu-latest, macos-latest, windows-latest]\n\n    runs-on: ${{ matrix.version }}\n    name: action on ${{ matrix.version }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup flux\n        uses: ./action\n"
  },
  {
    "path": ".github/workflows/backport.yaml",
    "content": "name: backport\non:\n  pull_request_target:\n    types: [closed, labeled]\npermissions: read-all\njobs:\n  backport:\n    permissions:\n      contents: write # for reading and creating branches.\n      pull-requests: write # for creating pull requests against release branches.\n    uses: fluxcd/gha-workflows/.github/workflows/backport.yaml@v0.9.0\n    secrets:\n      github-token: ${{ secrets.BOT_GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/conformance.yaml",
    "content": "name: conformance\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ 'main', 'update-components-**', 'release/**', 'conform*' ]\n\npermissions:\n  contents: read\n\nenv:\n  GO_VERSION: 1.26.x\n\njobs:\n  conform-kubernetes:\n    runs-on:\n      group: \"ARM64\"\n    strategy:\n      matrix:\n        # Keep this list up-to-date with https://endoflife.date/kubernetes\n        # Build images with https://github.com/fluxcd/flux-benchmark/actions/workflows/build-kind.yaml\n        KUBERNETES_VERSION: [1.33.0, 1.34.1, 1.35.0]\n      fail-fast: false\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: ${{ env.GO_VERSION }}\n          cache-dependency-path: |\n            **/go.sum\n            **/go.mod\n      - name: Prepare\n        id: prep\n        run: |\n          ID=${GITHUB_SHA:0:7}-${{ matrix.KUBERNETES_VERSION }}-$(date +%s)\n          echo \"CLUSTER=arm64-${ID}\" >> $GITHUB_OUTPUT\n      - name: Build\n        run: |\n          make build\n      - name: Setup Kubernetes\n        uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0\n        with:\n          version: v0.30.0\n          cluster_name: ${{ steps.prep.outputs.CLUSTER }}\n          node_image: ghcr.io/fluxcd/kindest/node:v${{ matrix.KUBERNETES_VERSION }}-arm64\n      - name: Run e2e tests\n        run: TEST_KUBECONFIG=$HOME/.kube/config make e2e\n      - name: Run multi-tenancy tests\n        run: |\n          ./bin/flux install\n          ./bin/flux create source git flux-system \\\n          --interval=15m \\\n          --url=https://github.com/fluxcd/flux2-multi-tenancy \\\n          --branch=main \\\n          --ignore-paths=\"./clusters/**/flux-system/\"\n          ./bin/flux create kustomization flux-system \\\n          --interval=15m \\\n          --source=flux-system \\\n          --path=./clusters/staging\n          kubectl -n flux-system wait kustomization/tenants --for=condition=ready --timeout=5m\n          kubectl -n apps wait kustomization/dev-team --for=condition=ready --timeout=1m\n          kubectl -n apps wait helmrelease/podinfo --for=condition=ready --timeout=1m\n      - name: Debug failure\n        if: failure()\n        run: |\n          kubectl -n flux-system get all\n          kubectl -n flux-system describe po \n          kubectl -n flux-system logs deploy/source-controller\n          kubectl -n flux-system logs deploy/kustomize-controller\n\n  conform-k3s:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        # Keep this list up-to-date with https://endoflife.date/kubernetes\n        # Available versions can be found with \"replicated cluster versions\"\n        K3S_VERSION: [ 1.33.7, 1.34.3, 1.35.0 ]\n      fail-fast: false\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: ${{ env.GO_VERSION }}\n          cache-dependency-path: |\n            **/go.sum\n            **/go.mod\n      - name: Prepare\n        id: prep\n        run: |\n          ID=${GITHUB_SHA:0:7}-${{ matrix.K3S_VERSION }}-$(date +%s)\n          PSEUDO_RAND_SUFFIX=$(echo \"${ID}\" | shasum | awk '{print $1}')\n          echo \"cluster=flux2-k3s-${PSEUDO_RAND_SUFFIX}\" >> $GITHUB_OUTPUT\n          KUBECONFIG_PATH=\"$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml\"\n          echo \"kubeconfig-path=${KUBECONFIG_PATH}\" >> $GITHUB_OUTPUT\n      - name: Setup Kustomize\n        uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main\n      - name: Build\n        run: make build-dev\n      - name: Create repository\n        run: |\n          gh repo create --private --add-readme fluxcd-testing/${{ steps.prep.outputs.cluster }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}\n      - name: Create cluster\n        id: create-cluster\n        uses: replicatedhq/replicated-actions/create-cluster@1abb33f5274580b14f49f2a12d819df7920e4d9b # v1.20.0\n        with:\n          api-token: ${{ secrets.REPLICATED_API_TOKEN }}\n          kubernetes-distribution: \"k3s\"\n          kubernetes-version: ${{ matrix.K3S_VERSION }}\n          ttl: 20m\n          cluster-name: \"${{ steps.prep.outputs.cluster }}\"\n          kubeconfig-path: ${{ steps.prep.outputs.kubeconfig-path }}\n          export-kubeconfig: true\n      - name: Run e2e tests\n        run: TEST_KUBECONFIG=${{ steps.prep.outputs.kubeconfig-path }} make e2e\n      - name: Run flux bootstrap\n        run: |\n          ./bin/flux bootstrap git --manifests ./manifests/test/ \\\n          --url=https://github.com/fluxcd-testing/${{ steps.prep.outputs.cluster }} \\\n          --branch=main \\\n          --path=clusters/k3s \\\n          --token-auth\n        env:\n          GIT_PASSWORD: ${{ secrets.GITPROVIDER_BOT_TOKEN }}\n      - name: Run flux check\n        run: |\n          ./bin/flux check\n      - name: Run flux reconcile\n        run: |\n          ./bin/flux reconcile ks flux-system --with-source\n          ./bin/flux get all\n          ./bin/flux events\n      - name: Collect reconcile logs\n        if: ${{ always() }}\n        continue-on-error: true\n        run: |\n          kubectl -n flux-system get all\n          kubectl -n flux-system describe pods\n          kubectl -n flux-system logs deploy/source-controller\n          kubectl -n flux-system logs deploy/kustomize-controller\n          kubectl -n flux-system logs deploy/notification-controller\n      - name: Delete flux\n        run: |\n          ./bin/flux uninstall -s --keep-namespace\n          kubectl delete ns flux-system --wait\n      - name: Delete cluster\n        if: ${{ always() }}\n        uses: replicatedhq/replicated-actions/remove-cluster@1abb33f5274580b14f49f2a12d819df7920e4d9b # v1.20.0\n        continue-on-error: true\n        with:\n          api-token: ${{ secrets.REPLICATED_API_TOKEN }}\n          cluster-id: ${{ steps.create-cluster.outputs.cluster-id }}\n      - name: Delete repository\n        if: ${{ always() }}\n        continue-on-error: true\n        run: |\n          gh repo delete fluxcd-testing/${{ steps.prep.outputs.cluster }} --yes\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}\n\n  conform-openshift:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        # Keep this list up-to-date with https://endoflife.date/red-hat-openshift\n        OPENSHIFT_VERSION: [ 4.20.0-okd ]\n      fail-fast: false\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: ${{ env.GO_VERSION }}\n          cache-dependency-path: |\n            **/go.sum\n            **/go.mod\n      - name: Prepare\n        id: prep\n        run: |\n          ID=${GITHUB_SHA:0:7}-${{ matrix.OPENSHIFT_VERSION }}-$(date +%s)\n          PSEUDO_RAND_SUFFIX=$(echo \"${ID}\" | shasum | awk '{print $1}')\n          echo \"cluster=flux2-openshift-${PSEUDO_RAND_SUFFIX}\" >> $GITHUB_OUTPUT\n          KUBECONFIG_PATH=\"$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml\"\n          echo \"kubeconfig-path=${KUBECONFIG_PATH}\" >> $GITHUB_OUTPUT\n      - name: Setup Kustomize\n        uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main\n      - name: Build\n        run: make build-dev\n      - name: Create repository\n        run: |\n          gh repo create --private --add-readme fluxcd-testing/${{ steps.prep.outputs.cluster }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}\n      - name: Create cluster\n        id: create-cluster\n        uses: replicatedhq/replicated-actions/create-cluster@1abb33f5274580b14f49f2a12d819df7920e4d9b # v1.20.0\n        with:\n          api-token: ${{ secrets.REPLICATED_API_TOKEN }}\n          kubernetes-distribution: \"openshift\"\n          kubernetes-version: ${{ matrix.OPENSHIFT_VERSION }}\n          ttl: 20m\n          cluster-name: \"${{ steps.prep.outputs.cluster }}\"\n          kubeconfig-path: ${{ steps.prep.outputs.kubeconfig-path }}\n          export-kubeconfig: true\n      - name: Run flux bootstrap\n        run: |\n          ./bin/flux bootstrap git --manifests ./manifests/openshift/ \\\n          --url=https://github.com/fluxcd-testing/${{ steps.prep.outputs.cluster }} \\\n          --branch=main \\\n          --path=clusters/openshift \\\n          --token-auth\n        env:\n          GIT_PASSWORD: ${{ secrets.GITPROVIDER_BOT_TOKEN }}\n      - name: Run flux check\n        run: |\n          ./bin/flux check\n      - name: Run flux reconcile\n        run: |\n          ./bin/flux reconcile ks flux-system --with-source\n          ./bin/flux get all\n          ./bin/flux events\n      - name: Collect reconcile logs\n        if: ${{ always() }}\n        continue-on-error: true\n        run: |\n          kubectl -n flux-system get all\n          kubectl -n flux-system describe pods\n          kubectl -n flux-system logs deploy/source-controller\n          kubectl -n flux-system logs deploy/kustomize-controller\n          kubectl -n flux-system logs deploy/notification-controller\n      - name: Delete flux\n        run: |\n          ./bin/flux uninstall -s --keep-namespace\n          kubectl delete ns flux-system --wait\n      - name: Delete cluster\n        if: ${{ always() }}\n        uses: replicatedhq/replicated-actions/remove-cluster@1abb33f5274580b14f49f2a12d819df7920e4d9b # v1.20.0\n        continue-on-error: true\n        with:\n          api-token: ${{ secrets.REPLICATED_API_TOKEN }}\n          cluster-id: ${{ steps.create-cluster.outputs.cluster-id }}\n      - name: Delete repository\n        if: ${{ always() }}\n        continue-on-error: true\n        run: |\n          gh repo delete fluxcd-testing/${{ steps.prep.outputs.cluster }} --yes\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/e2e-azure.yaml",
    "content": "name: e2e-azure\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '0 6 * * *'\n  push:\n    branches:\n      - main\n    paths:\n      - 'tests/**'\n      - '.github/workflows/e2e-azure.yaml'\n  pull_request:\n    branches:\n      - main\n    paths:\n      - 'tests/**'\n      - '.github/workflows/e2e-azure.yaml'\n\npermissions:\n  contents: read\n\njobs:\n  e2e-aks:\n    runs-on: ubuntu-latest\n    defaults:\n      run:\n        working-directory: ./tests/integration\n    if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'\n    steps:\n      - name: CheckoutD\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: 1.26.x\n          cache-dependency-path: tests/integration/go.sum\n      - name: Setup Terraform\n        uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 # v4.0.0\n      - name: Setup Flux CLI\n        run: make build\n        working-directory: ./\n      - name: Setup SOPS\n        run: |\n          mkdir -p $HOME/.local/bin\n          wget -O $HOME/.local/bin/sops https://github.com/mozilla/sops/releases/download/v$SOPS_VER/sops-v$SOPS_VER.linux\n          chmod +x $HOME/.local/bin/sops\n        env:\n          SOPS_VER: 3.7.1\n      - name: Authenticate to Azure\n        uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v1.4.6\n        with:\n          creds: '{\"clientId\":\"${{ secrets.ARM_CLIENT_ID }}\",\"clientSecret\":\"${{ secrets.ARM_CLIENT_SECRET }}\",\"subscriptionId\":\"${{ secrets.ARM_SUBSCRIPTION_ID }}\",\"tenantId\":\"${{ secrets.ARM_TENANT_ID }}\"}'\n      - name: Set dynamic variables in .env\n        run: |\n          cat > .env <<EOF\n          export TF_VAR_tags='{ \"environment\"=\"github\", \"ci\"=\"true\", \"repo\"=\"flux2\", \"createdat\"=\"$(date -u +x%Y-%m-%d_%Hh%Mm%Ss)\" }'\n          EOF\n      - name: Print .env for dynamic tag value reference\n        run: cat .env\n      - name: Run Azure e2e tests\n        env:\n          ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}\n          ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}\n          ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}\n          ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}\n          TF_VAR_azuredevops_org: ${{ secrets.TF_VAR_azuredevops_org }}\n          TF_VAR_azuredevops_pat: ${{ secrets.TF_VAR_azuredevops_pat }}\n          TF_VAR_azure_location: ${{ vars.TF_VAR_azure_location }}\n          GITREPO_SSH_CONTENTS: ${{ secrets.GIT_SSH_IDENTITY }}\n          GITREPO_SSH_PUB_CONTENTS: ${{ secrets.GIT_SSH_IDENTITY_PUB }}\n        run: |\n          source .env\n          mkdir -p ./build/ssh\n          cat <<EOF > build/ssh/key\n          $GITREPO_SSH_CONTENTS\n          EOF\n          export GITREPO_SSH_PATH=build/ssh/key\n          cat <<EOF > build/ssh/key.pub\n          $GITREPO_SSH_PUB_CONTENTS\n          EOF\n          export GITREPO_SSH_PUB_PATH=build/ssh/key.pub\n          make test-azure\n      - name: Ensure resource cleanup\n        if: ${{ always() }}\n        env:\n          ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}\n          ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}\n          ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}\n          ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}\n          TF_VAR_azuredevops_org: ${{ secrets.TF_VAR_azuredevops_org }}\n          TF_VAR_azuredevops_pat: ${{ secrets.TF_VAR_azuredevops_pat }}\n          TF_VAR_azure_location: ${{ vars.TF_VAR_azure_location }}\n        run: source .env && make destroy-azure\n"
  },
  {
    "path": ".github/workflows/e2e-bootstrap.yaml",
    "content": "name: e2e-bootstrap\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ 'main', 'release/**' ]\n  pull_request:\n    branches: [ 'main', 'release/**' ]\n    paths-ignore: [ 'docs/**', 'rfcs/**' ]\n\npermissions:\n  contents: read\n\njobs:\n  e2e-boostrap-github:\n    runs-on: ubuntu-latest\n    if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: 1.26.x\n          cache-dependency-path: |\n            **/go.sum\n            **/go.mod\n      - name: Setup Kubernetes\n        uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0\n        with:\n          version: v0.30.0\n          cluster_name: kind\n          # The versions below should target the newest Kubernetes version\n          # Keep this up-to-date with https://endoflife.date/kubernetes\n          node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64\n          kubectl_version: v1.33.0\n      - name: Setup Kustomize\n        uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main\n      - name: Setup yq\n        uses: fluxcd/pkg/actions/yq@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main\n      - name: Build\n        run: make build-dev\n      - name: Set outputs\n        id: vars\n        run: |\n          REPOSITORY_NAME=${{ github.event.repository.name }}\n          BRANCH_NAME=${GITHUB_REF##*/}\n          COMMIT_SHA=$(git rev-parse HEAD)\n          PSEUDO_RAND_SUFFIX=$(echo \"${BRANCH_NAME}-${COMMIT_SHA}\" | shasum | awk '{print $1}')\n          TEST_REPO_NAME=\"${REPOSITORY_NAME}-${PSEUDO_RAND_SUFFIX}\"\n          echo \"test_repo_name=$TEST_REPO_NAME\" >> $GITHUB_OUTPUT\n      - name: bootstrap init\n        run: |\n          ./bin/flux bootstrap github --manifests ./manifests/test/ \\\n          --owner=fluxcd-testing \\\n          --image-pull-secret=ghcr-auth \\\n          --registry-creds=fluxcd:$GITHUB_TOKEN \\\n          --repository=${{ steps.vars.outputs.test_repo_name }} \\\n          --branch=main \\\n          --path=test-cluster \\\n          --team=team-z\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}\n      - name: verify image pull secret\n        run: |\n          kubectl -n flux-system get secret ghcr-auth | grep dockerconfigjson\n      - name: bootstrap no-op\n        run: |\n          ./bin/flux bootstrap github --manifests ./manifests/test/ \\\n          --owner=fluxcd-testing \\\n          --image-pull-secret=ghcr-auth \\\n          --repository=${{ steps.vars.outputs.test_repo_name }} \\\n          --branch=main \\\n          --path=test-cluster \\\n          --team=team-z\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}\n      - name: bootstrap customize\n        run: |\n          make setup-bootstrap-patch\n          ./bin/flux bootstrap github --manifests ./manifests/test/ \\\n          --owner=fluxcd-testing \\\n          --repository=${{ steps.vars.outputs.test_repo_name }} \\\n          --branch=main \\\n          --path=test-cluster \\\n          --team=team-z\n          if [ $(kubectl get deployments.apps source-controller -o jsonpath='{.spec.template.spec.securityContext.runAsUser}') != \"10000\" ]; then\n          echo \"Bootstrap not customized as controller is not running as user 10000\" && exit 1\n          fi\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}\n          GITHUB_REPO_NAME: ${{ steps.vars.outputs.test_repo_name }}\n          GITHUB_ORG_NAME: fluxcd-testing\n      - name: uninstall\n        run: |\n          ./bin/flux uninstall -s --keep-namespace\n          kubectl delete ns flux-system --timeout=10m --wait=true\n      - name: test image automation\n        run: |\n          make setup-image-automation\n          ./bin/flux bootstrap github --manifests ./manifests/test/ \\\n          --owner=fluxcd-testing \\\n          --repository=${{ steps.vars.outputs.test_repo_name }} \\\n          --branch=main \\\n          --path=test-cluster \\\n          --read-write-key\n          ./bin/flux reconcile image repository podinfo\n          ./bin/flux reconcile image policy podinfo\n          ./bin/flux reconcile image update flux-system\n          ./bin/flux get images all\n          ./bin/flux -n flux-system events --for ImageUpdateAutomation/flux-system\n          kubectl -n flux-system get -o yaml ImageUpdateAutomation flux-system\n          kubectl -n flux-system get -o yaml ImageUpdateAutomation flux-system | \\\n           yq '.status.lastPushCommit | length > 1' | grep 'true'\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}\n          GITHUB_REPO_NAME: ${{ steps.vars.outputs.test_repo_name }}\n          GITHUB_ORG_NAME: fluxcd-testing\n      - name: delete repository\n        if: ${{ always() }}\n        continue-on-error: true\n        run: |\n          gh repo delete fluxcd-testing/${{ steps.vars.outputs.test_repo_name }} --yes\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}\n      - name: Debug failure\n        if: failure()\n        run: |\n          kubectl -n flux-system get all\n          kubectl -n flux-system logs deploy/source-controller\n          kubectl -n flux-system logs deploy/kustomize-controller\n"
  },
  {
    "path": ".github/workflows/e2e-gcp.yaml",
    "content": "name: e2e-gcp\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '0 6 * * *'\n  push:\n    branches:\n      - main\n    paths:\n      - 'tests/**'\n      - '.github/workflows/e2e-gcp.yaml'\n  pull_request:\n    branches:\n      - main\n    paths:\n      - 'tests/**'\n      - '.github/workflows/e2e-gcp.yaml'\n\npermissions:\n  contents: read\n\njobs:\n  e2e-gcp:\n    runs-on: ubuntu-latest\n    defaults:\n      run:\n        working-directory: ./tests/integration\n    if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: 1.26.x\n          cache-dependency-path: tests/integration/go.sum\n      - name: Setup Terraform\n        uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 # v4.0.0\n      - name: Setup Flux CLI\n        run: make build\n        working-directory: ./\n      - name: Setup SOPS\n        run: |\n          mkdir -p $HOME/.local/bin\n          wget -O $HOME/.local/bin/sops https://github.com/mozilla/sops/releases/download/v$SOPS_VER/sops-v$SOPS_VER.linux\n          chmod +x $HOME/.local/bin/sops\n        env:\n          SOPS_VER: 3.7.1\n      - name: Authenticate to Google Cloud\n        uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0\n        id: 'auth'\n        with:\n          credentials_json: '${{ secrets.FLUX2_E2E_GOOGLE_CREDENTIALS }}'\n          token_format: 'access_token'\n      - name: Setup gcloud\n        uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1\n      - name: Setup QEMU\n        uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0\n      - name: Setup Docker Buildx\n        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0\n      - name: Log into us-central1-docker.pkg.dev\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          registry: us-central1-docker.pkg.dev\n          username: oauth2accesstoken\n          password: ${{ steps.auth.outputs.access_token }}\n      - name: Set dynamic variables in .env\n        run: |\n          cat > .env <<EOF\n          export TF_VAR_tags='{ \"environment\"=\"github\", \"ci\"=\"true\", \"repo\"=\"flux2\", \"createdat\"=\"$(date -u +x%Y-%m-%d_%Hh%Mm%Ss)\" }'\n          EOF\n      - name: Print .env for dynamic tag value reference\n        run: cat .env\n      - name: Run GCP e2e tests\n        env:\n          TF_VAR_gcp_project_id: ${{ vars.TF_VAR_gcp_project_id }}\n          TF_VAR_gcp_region: ${{ vars.TF_VAR_gcp_region }}\n          TF_VAR_gcp_zone: ${{ vars.TF_VAR_gcp_zone }}\n          TF_VAR_gcp_email: ${{ secrets.TF_VAR_gcp_email }}\n          TF_VAR_gcp_keyring: ${{ secrets.TF_VAR_gcp_keyring }}\n          TF_VAR_gcp_crypto_key: ${{ secrets.TF_VAR_gcp_crypto_key }}\n          GITREPO_SSH_CONTENTS: ${{ secrets.GCP_GITREPO_SSH_CONTENTS }}\n          GITREPO_SSH_PUB_CONTENTS: ${{ secrets.GCP_GITREPO_SSH_PUB_CONTENTS }}\n        run: |\n          source .env\n          mkdir -p ./build/ssh\n          touch ./build/ssh/key\n          echo $GITREPO_SSH_CONTENTS | base64 -d > build/ssh/key\n          export GITREPO_SSH_PATH=build/ssh/key\n          touch ./build/ssh/key.pub\n          echo $GITREPO_SSH_PUB_CONTENTS | base64 -d > ./build/ssh/key.pub\n          export GITREPO_SSH_PUB_PATH=build/ssh/key.pub\n          make test-gcp\n      - name: Ensure resource cleanup\n        if: ${{ always() }}\n        env:\n          TF_VAR_gcp_project_id: ${{ vars.TF_VAR_gcp_project_id }}\n          TF_VAR_gcp_region: ${{ vars.TF_VAR_gcp_region }}\n          TF_VAR_gcp_zone: ${{ vars.TF_VAR_gcp_zone }}\n          TF_VAR_gcp_email: ${{ secrets.TF_VAR_gcp_email }}\n          TF_VAR_gcp_keyring: ${{ secrets.TF_VAR_gcp_keyring }}\n          TF_VAR_gcp_crypto_key: ${{ secrets.TF_VAR_gcp_crypto_key }}\n        run: source .env && make destroy-gcp\n"
  },
  {
    "path": ".github/workflows/e2e.yaml",
    "content": "name: e2e\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ 'main', 'release/**' ]\n  pull_request:\n    branches: [ 'main', 'release/**' ]\n    paths-ignore: [ 'docs/**', 'rfcs/**' ]\n\npermissions:\n  contents: read\n\njobs:\n  e2e-amd64-kubernetes:\n    runs-on:\n      group: \"Default Larger Runners\"\n      labels: ubuntu-latest-16-cores\n    services:\n      registry:\n        image: registry:2\n        ports:\n          - 5000:5000\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: 1.26.x\n          cache-dependency-path: |\n            **/go.sum\n            **/go.mod\n      - name: Setup Kubernetes\n        uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0\n        with:\n          version: v0.30.0\n          cluster_name: kind\n          wait: 5s\n          config: .github/kind/config.yaml # disable KIND-net\n          # The versions below should target the oldest supported Kubernetes version\n          # Keep this up-to-date with https://endoflife.date/kubernetes\n          node_image: ghcr.io/fluxcd/kindest/node:v1.33.0-amd64\n          kubectl_version: v1.33.0\n      - name: Setup Calico for network policy\n        run: |\n          kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml\n      - name: Setup Kustomize\n        uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main\n      - name: Run tests\n        run: make test\n      - name: Run e2e tests\n        run: TEST_KUBECONFIG=$HOME/.kube/config make e2e\n      - name: Check if working tree is dirty\n        run: |\n          if [[ $(git diff --stat) != '' ]]; then\n            git diff\n            echo 'run make test and commit changes'\n            exit 1\n          fi\n      - name: Build\n        run: make build-dev\n      - name: flux check --pre\n        run: |\n          ./bin/flux check --pre\n      - name: flux install --manifests\n        run: |\n          ./bin/flux install --manifests ./manifests/test/\n      - name: flux create secret\n        run: |\n          ./bin/flux create secret git git-ssh-test \\\n            --url ssh://git@github.com/stefanprodan/podinfo\n          ./bin/flux create secret git git-https-test \\\n            --url https://github.com/stefanprodan/podinfo \\\n            --username=test --password=test\n          ./bin/flux create secret helm helm-test \\\n            --username=test --password=test\n      - name: flux create source git\n        run: |\n          ./bin/flux create source git podinfo \\\n            --url https://github.com/stefanprodan/podinfo  \\\n            --tag-semver=\">=6.3.5\"\n      - name: flux create source git export apply\n        run: |\n          ./bin/flux create source git podinfo-export \\\n            --url https://github.com/stefanprodan/podinfo  \\\n            --tag-semver=\">=6.3.5\" \\\n            --export | kubectl apply -f -\n          ./bin/flux delete source git podinfo-export --silent\n      - name: flux get sources git\n        run: |\n          ./bin/flux get sources git\n      - name: flux get sources git --all-namespaces\n        run: |\n          ./bin/flux get sources git --all-namespaces\n      - name: flux create kustomization\n        run: |\n          ./bin/flux create kustomization podinfo \\\n            --source=podinfo \\\n            --path=\"./deploy/overlays/dev\" \\\n            --prune=true \\\n            --interval=5m \\\n            --health-check=\"Deployment/frontend.dev\" \\\n            --health-check=\"Deployment/backend.dev\" \\\n            --health-check-timeout=3m\n      - name: flux trace\n        run: |\n          ./bin/flux trace frontend \\\n            --kind=deployment \\\n            --api-version=apps/v1 \\\n            --namespace=dev\n      - name: flux reconcile kustomization --with-source\n        run: |\n          ./bin/flux reconcile kustomization podinfo --with-source\n      - name: flux get kustomizations\n        run: |\n          ./bin/flux get kustomizations\n      - name: flux get kustomizations --all-namespaces\n        run: |\n          ./bin/flux get kustomizations --all-namespaces\n      - name: flux suspend kustomization\n        run: |\n          ./bin/flux suspend kustomization podinfo\n      - name: flux resume kustomization\n        run: |\n          ./bin/flux resume kustomization podinfo\n      - name: flux export\n        run: |\n          ./bin/flux export source git --all\n          ./bin/flux export kustomization --all\n      - name: flux delete kustomization\n        run: |\n          ./bin/flux delete kustomization podinfo --silent\n      - name: flux create source helm\n        run: |\n          ./bin/flux create source helm podinfo \\\n            --url https://stefanprodan.github.io/podinfo\n      - name: flux create helmrelease --source=HelmRepository/podinfo\n        run: |\n          ./bin/flux create hr podinfo-helm \\\n            --target-namespace=default \\\n            --source=HelmRepository/podinfo.flux-system \\\n            --chart=podinfo \\\n            --chart-version=\">6.0.0 <7.0.0\"\n      - name: flux create helmrelease --source=GitRepository/podinfo\n        run: |\n          ./bin/flux create hr podinfo-git \\\n            --target-namespace=default \\\n            --source=GitRepository/podinfo \\\n            --chart=./charts/podinfo\n      - name: flux reconcile helmrelease --with-source\n        run: |\n          ./bin/flux reconcile helmrelease podinfo-git --with-source\n      - name: flux get helmreleases\n        run: |\n          ./bin/flux get helmreleases\n      - name: flux get helmreleases --all-namespaces\n        run: |\n          ./bin/flux get helmreleases --all-namespaces\n      - name: flux export helmrelease\n        run: |\n          ./bin/flux export hr --all\n      - name: flux delete helmrelease podinfo-helm\n        run: |\n          ./bin/flux delete hr podinfo-helm --silent\n      - name: flux delete helmrelease podinfo-git\n        run: |\n          ./bin/flux delete hr podinfo-git --silent\n      - name: flux delete source helm\n        run: |\n          ./bin/flux delete source helm podinfo --silent\n      - name: flux delete source git\n        run: |\n          ./bin/flux delete source git podinfo --silent\n      - name: flux oci artifacts\n        run: |\n          ./bin/flux push artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \\\n            --path=\"./manifests\" \\\n            --source=\"${{ github.repositoryUrl }}\" \\\n            --revision=\"${{ github.ref }}@sha1:${{ github.sha }}\"\n          ./bin/flux tag artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \\\n            --tag latest\n          ./bin/flux list artifacts oci://localhost:5000/fluxcd/flux\n      - name: flux oci repositories\n        run: |\n          ./bin/flux create source oci podinfo-oci \\\n            --url oci://ghcr.io/stefanprodan/manifests/podinfo \\\n            --tag-semver 6.3.x \\\n            --interval 10m\n          ./bin/flux create kustomization podinfo-oci \\\n            --source=OCIRepository/podinfo-oci \\\n            --path=\"./\" \\\n            --prune=true \\\n            --interval=5m \\\n            --target-namespace=default \\\n            --wait=true \\\n            --health-check-timeout=3m\n          ./bin/flux reconcile source oci podinfo-oci\n          ./bin/flux suspend source oci podinfo-oci\n          ./bin/flux get sources oci\n          ./bin/flux resume source oci podinfo-oci\n          ./bin/flux export source oci podinfo-oci\n          ./bin/flux delete ks podinfo-oci --silent\n          ./bin/flux delete source oci podinfo-oci --silent\n      - name: flux create tenant\n        run: |\n          ./bin/flux create tenant dev-team --with-namespace=apps\n          ./bin/flux -n apps create source helm podinfo \\\n            --url https://stefanprodan.github.io/podinfo\n          ./bin/flux -n apps create hr podinfo-helm \\\n            --source=HelmRepository/podinfo \\\n            --chart=podinfo \\\n            --chart-version=\"6.3.x\" \\\n            --service-account=dev-team\n      - name: flux2-kustomize-helm-example\n        run: |\n          ./bin/flux create source git flux-system \\\n          --url=https://github.com/fluxcd/flux2-kustomize-helm-example \\\n          --branch=main \\\n          --ignore-paths=\"./clusters/**/flux-system/\" \\\n          --recurse-submodules\n          ./bin/flux create kustomization flux-system \\\n          --source=flux-system \\\n          --path=./clusters/staging\n          kubectl -n flux-system wait kustomization/infra-controllers --for=condition=ready --timeout=5m\n          kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=5m\n          kubectl -n podinfo wait helmrelease/podinfo --for=condition=ready --timeout=5m\n      - name: flux tree\n        run: |\n          ./bin/flux tree kustomization flux-system | grep Service/podinfo\n      - name: flux events\n        run: |\n          ./bin/flux -n flux-system events --for Kustomization/apps | grep 'HelmRelease/podinfo'\n          ./bin/flux -n podinfo events --for HelmRelease/podinfo | grep 'podinfo.v1'\n      - name: flux stats\n        run: |\n          ./bin/flux stats -A\n      - name: flux check\n        run: |\n          ./bin/flux check\n      - name: flux migrate\n        run: |\n          ./bin/flux migrate\n      - name: flux version\n        run: |\n          ./bin/flux version\n      - name: flux uninstall\n        run: |\n          ./bin/flux uninstall --silent\n      - name: Debug failure\n        if: failure()\n        run: |\n          kubectl version --client\n          kubectl -n flux-system get all\n          kubectl -n flux-system describe pods\n          kubectl -n flux-system get kustomizations -oyaml\n          kubectl -n flux-system logs deploy/source-controller\n          kubectl -n flux-system logs deploy/kustomize-controller\n"
  },
  {
    "path": ".github/workflows/ossf.yaml",
    "content": "name: ossf\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n  schedule:\n    # Weekly on Saturdays.\n    - cron:  '30 1 * * 6'\n\npermissions: read-all\n\njobs:\n  scorecard:\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n      id-token: write\n      actions: read\n      contents: read\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Run analysis\n        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          publish_results: true\n      - name: Upload artifact\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: SARIF file\n          path: results.sarif\n          retention-days: 5\n      - name: Upload SARIF results\n        uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: release\n\non:\n  push:\n    tags: [\"v*\"]\n\npermissions:\n  contents: read\n\njobs:\n  release-flux-cli:\n    outputs:\n      hashes: ${{ steps.slsa.outputs.hashes }}\n      image_url: ${{ steps.slsa.outputs.image_url }}\n      image_digest: ${{ steps.slsa.outputs.image_digest }}\n    runs-on:\n      group: \"Default Larger Runners\"\n      labels: ubuntu-latest-16-cores\n    permissions:\n      contents: write # needed to write releases\n      id-token: write # needed for keyless signing\n      packages: write # needed for ghcr access\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Unshallow\n        run: git fetch --prune --unshallow\n      - name: Setup Go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: 1.26.x\n          cache: false\n      - name: Setup QEMU\n        uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0\n      - name: Setup Docker Buildx\n        id: buildx\n        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0\n      - name: Setup Syft\n        uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0.23.1\n      - name: Setup Cosign\n        uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0\n        with:\n          cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3\n      - name: Setup Kustomize\n        uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          registry: ghcr.io\n          username: fluxcdbot\n          password: ${{ secrets.GITHUB_TOKEN }}\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: fluxcdbot\n          password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}\n      - name: Generate manifests\n        run: |\n          make cmd/flux/.manifests.done\n          ./manifests/scripts/bundle.sh \"\" ./output manifests.tar.gz\n          kustomize build ./manifests/install > ./output/install.yaml\n      - name: Build CRDs\n        run: |\n          kustomize build manifests/crds > all-crds.yaml\n      - name: Generate OpenAPI JSON schemas from CRDs\n        uses: fluxcd/pkg/actions/crdjsonschema@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main\n        with:\n          crd: all-crds.yaml\n          output: schemas\n      - name: Archive the OpenAPI JSON schemas\n        run: |\n          tar -czvf ./output/crd-schemas.tar.gz -C schemas .\n      - name: Run GoReleaser\n        id: run-goreleaser\n        uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0\n        with:\n          version: latest\n          args: release --skip=validate\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}\n          AUR_BOT_SSH_PRIVATE_KEY: ${{ secrets.AUR_BOT_SSH_PRIVATE_KEY }}\n      - name: Generate SLSA metadata\n        id: slsa\n        env:\n          ARTIFACTS: \"${{ steps.run-goreleaser.outputs.artifacts }}\"\n        run: |\n          set -euo pipefail\n\n          hashes=$(echo -E $ARTIFACTS | jq --raw-output '.[] | {name, \"digest\": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join(\"  \") | sub(\"^sha256:\";\"\")' | base64 -w0)\n          echo \"hashes=$hashes\" >> $GITHUB_OUTPUT\n\n          image_url=fluxcd/flux-cli:$GITHUB_REF_NAME\n          echo \"image_url=$image_url\" >> $GITHUB_OUTPUT\n\n          image_digest=$(docker buildx imagetools inspect ${image_url}  --format '{{json .}}' | jq -r .manifest.digest)\n          echo \"image_digest=$image_digest\" >> $GITHUB_OUTPUT\n\n  release-flux-manifests:\n    runs-on: ubuntu-latest\n    needs: release-flux-cli\n    permissions:\n      id-token: write\n      packages: write\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup Kustomize\n        uses: fluxcd/pkg/actions/kustomize@9a8c0edd5da84dc51a585738c67e3a3950d7fbf0 # main\n      - name: Setup Flux CLI\n        uses: ./action/\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n      - name: Prepare\n        id: prep\n        run: |\n          VERSION=$(flux version --client | awk '{ print $NF }')\n          echo \"version=${VERSION}\" >> $GITHUB_OUTPUT\n      - name: Login to GHCR\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          registry: ghcr.io\n          username: fluxcdbot\n          password: ${{ secrets.GITHUB_TOKEN }}\n      - name: Login to DockerHub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: fluxcdbot\n          password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}\n      - name: Push manifests to GHCR\n        run: |\n          mkdir -p ./ghcr.io/flux-system\n          flux install --registry=ghcr.io/fluxcd \\\n          --components-extra=image-reflector-controller,image-automation-controller \\\n          --export > ./ghcr.io/flux-system/gotk-components.yaml\n\n          cd ./ghcr.io && flux push artifact \\\n          oci://ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \\\n          --path=\"./flux-system\" \\\n          --source=${{ github.repositoryUrl }} \\\n          --revision=\"${{ github.ref_name }}@sha1:${{ github.sha }}\"\n      - name: Push manifests to DockerHub\n        run: |\n          mkdir -p ./docker.io/flux-system\n          flux install --registry=docker.io/fluxcd \\\n          --components-extra=image-reflector-controller,image-automation-controller \\\n          --export > ./docker.io/flux-system/gotk-components.yaml\n\n          cd ./docker.io && flux push artifact \\\n          oci://docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \\\n          --path=\"./flux-system\" \\\n          --source=${{ github.repositoryUrl }} \\\n          --revision=\"${{ github.ref_name }}@sha1:${{ github.sha }}\"\n      - uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0\n        with:\n          cosign-release: v2.6.1 # TODO: remove after Flux 2.8 with support for cosign v3\n      - name: Sign manifests\n        env:\n          COSIGN_EXPERIMENTAL: 1\n        run: |\n          cosign sign --yes ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }}\n          cosign sign --yes docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }}\n      - name: Tag manifests\n        run: |\n          flux tag artifact oci://ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \\\n          --tag latest\n\n          flux tag artifact oci://docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \\\n          --tag latest\n\n  release-provenance:\n    needs: [release-flux-cli]\n    permissions:\n      actions: read # for detecting the Github Actions environment.\n      id-token: write # for creating OIDC tokens for signing.\n      contents: write # for uploading attestations to GitHub releases.\n    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0\n    with:\n      provenance-name: \"provenance.intoto.jsonl\"\n      base64-subjects: \"${{ needs.release-flux-cli.outputs.hashes }}\"\n      upload-assets: true\n\n  dockerhub-provenance:\n    needs: [release-flux-cli]\n    permissions:\n      actions: read # for detecting the Github Actions environment.\n      id-token: write # for creating OIDC tokens for signing.\n      packages: write # for uploading attestations.\n    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0\n    with:\n      image: ${{ needs.release-flux-cli.outputs.image_url }}\n      digest: ${{ needs.release-flux-cli.outputs.image_digest }}\n      registry-username: fluxcdbot\n    secrets:\n      registry-password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}\n\n  ghcr-provenance:\n    needs: [release-flux-cli]\n    permissions:\n      actions: read # for detecting the Github Actions environment.\n      id-token: write # for creating OIDC tokens for signing.\n      packages: write # for uploading attestations.\n    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0\n    with:\n      image: ghcr.io/${{ needs.release-flux-cli.outputs.image_url }}\n      digest: ${{ needs.release-flux-cli.outputs.image_digest }}\n      registry-username: fluxcdbot\n    secrets:\n      registry-password: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/scan.yaml",
    "content": "name: scan\non:\n  workflow_dispatch:\n  push:\n    branches: [ 'main', 'release/**' ]\n  pull_request:\n    branches: [ 'main', 'release/**' ]\n  schedule:\n    - cron: '18 10 * * 3'\npermissions: read-all\njobs:\n  analyze:\n    permissions:\n      contents: read # for reading the repository code.\n      security-events: write # for uploading the CodeQL analysis results.\n    uses: fluxcd/gha-workflows/.github/workflows/code-scan.yaml@v0.9.0\n    secrets:\n      github-token: ${{ secrets.GITHUB_TOKEN }}\n      fossa-token: ${{ secrets.FOSSA_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/sync-labels.yaml",
    "content": "name: sync-labels\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n    paths:\n      - .github/labels.yaml\npermissions: read-all\njobs:\n  sync-labels:\n    permissions:\n      contents: read # for reading the labels file.\n      issues: write # for creating and updating labels.\n    uses: fluxcd/gha-workflows/.github/workflows/labels-sync.yaml@v0.9.0\n    secrets:\n      github-token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/update.yaml",
    "content": "name: update\n\non:\n  workflow_dispatch:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n\njobs:\n  update-components:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      pull-requests: write\n    steps:\n      - name: Check out code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: 1.26.x\n          cache-dependency-path: |\n            **/go.sum\n            **/go.mod\n      - name: Update component versions\n        id: update\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          PR_BODY=$(mktemp)\n\n          bump_version() {\n            local LATEST_VERSION=$(curl -s -H \"Authorization: token ${GITHUB_TOKEN}\" https://api.github.com/repos/fluxcd/$1/releases | jq -r 'sort_by(.published_at) | .[-1] | .tag_name')\n\n            if [[ \"$LATEST_VERSION\" == *\"-rc\"* ]]; then\n              echo \"Skipping release candidate version for $1: $LATEST_VERSION\"\n              return\n            fi\n\n            local CTRL_VERSION=$(sed -n \"s/.*$1\\/releases\\/download\\/\\(.*\\)\\/.*/\\1/p;n\" manifests/bases/$1/kustomization.yaml)\n            local CRD_VERSION=$(sed -n \"s/.*$1\\/releases\\/download\\/\\(.*\\)\\/.*/\\1/p\" manifests/crds/kustomization.yaml)\n          \n            local API_PKG=\"github.com/fluxcd/$1/api\"\n            if [[ \"$1\" == \"source-watcher\" ]]; then\n              API_PKG=\"github.com/fluxcd/$1/api/v2\"\n            fi\n            local MOD_VERSION=$(go list -m -f '{{ .Version }}' \"$API_PKG\")\n\n            local changed=false\n\n            if [[ \"${CTRL_VERSION}\" != \"${LATEST_VERSION}\" ]]; then\n              sed -i \"s/\\($1\\/releases\\/download\\/\\)v.*\\(\\/.*\\)/\\1${LATEST_VERSION}\\2/g\" \"manifests/bases/$1/kustomization.yaml\"\n              changed=true\n            fi\n\n            if [[ \"${CRD_VERSION}\" != \"${LATEST_VERSION}\" ]]; then\n              sed -i \"s/\\($1\\/releases\\/download\\/\\)v.*\\(\\/.*\\)/\\1${LATEST_VERSION}\\2/g\" \"manifests/crds/kustomization.yaml\"\n              changed=true\n            fi\n\n            if [[ \"${MOD_VERSION}\" != \"${LATEST_VERSION}\" ]]; then\n              go mod edit -require=\"$API_PKG@${LATEST_VERSION}\"\n              make tidy\n              changed=true\n            fi\n\n            if [[ \"$changed\" == true ]]; then\n              echo \"- $1 to ${LATEST_VERSION}\" >> $PR_BODY\n              echo \"  https://github.com/fluxcd/$1/blob/${LATEST_VERSION}/CHANGELOG.md\" >> $PR_BODY\n            fi\n          }\n\n          {\n            # bump controller versions\n            bump_version helm-controller\n            bump_version kustomize-controller\n            bump_version source-controller\n            bump_version notification-controller\n            bump_version image-reflector-controller\n            bump_version image-automation-controller\n            bump_version source-watcher\n\n            # diff change\n            git diff\n\n            # export PR_BODY for PR and commit\n            # NB: this may look strange but it is the way it should be done to\n            # maintain our precious newlines\n            # Ref: https://github.com/github/docs/issues/21529\n            echo 'pr_body<<EOF' >> $GITHUB_OUTPUT\n            cat $PR_BODY >> $GITHUB_OUTPUT\n            echo 'EOF' >> $GITHUB_OUTPUT\n          }\n\n      - name: Create Pull Request\n        id: cpr\n        uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0\n        with:\n          token: ${{ secrets.BOT_GITHUB_TOKEN }}\n          commit-message: |\n            Update toolkit components\n\n            ${{ steps.update.outputs.pr_body }}\n          committer: GitHub <noreply@github.com>\n          author: fluxcdbot <fluxcdbot@users.noreply.github.com>\n          signoff: true\n          branch: update-components-${{ github.ref_name }}\n          title: Update toolkit components\n          body: |\n            ${{ steps.update.outputs.pr_body }}\n          labels: |\n            dependencies\n          reviewers: ${{ secrets.ASSIGNEES }}\n\n      - name: Check output\n        run: |\n          echo \"Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}\"\n          echo \"Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}\"\n"
  },
  {
    "path": ".github/workflows/upgrade-fluxcd-pkg.yaml",
    "content": "name: upgrade-fluxcd-pkg\n\non:\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\njobs:\n  upgrade-fluxcd-pkg:\n    uses: fluxcd/gha-workflows/.github/workflows/upgrade-fluxcd-pkg.yaml@v0.9.0\n    secrets:\n      github-token: ${{ secrets.BOT_GITHUB_TOKEN }}\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# Release\ndist/\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\nbin/\noutput/\ncmd/flux/manifests/\ncmd/flux/.manifests.done\ntestbin/\n\n# Docs\nsite/\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "project_name: flux\nchangelog:\n  use: github-native\nbuilds:\n  - <<: &build_defaults\n      binary: flux\n      main: ./cmd/flux\n      ldflags:\n        - -s -w -X main.VERSION={{ .Version }}\n      env:\n        - CGO_ENABLED=0\n    id: linux\n    goos:\n      - linux\n    goarch:\n      - amd64\n      - arm64\n      - arm\n    goarm:\n      - \"7\"\n  - <<: *build_defaults\n    id: darwin\n    goos:\n      - darwin\n    goarch:\n      - amd64\n      - arm64\n  - <<: *build_defaults\n    id: windows\n    goos:\n      - windows\narchives:\n  - name_template: \"{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}\"\n    id: nix\n    builds: [linux, darwin]\n    format: tar.gz\n    files:\n      - none*\n  - name_template: \"{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}\"\n    id: windows\n    builds: [windows]\n    format: zip\n    files:\n      - none*\nsource:\n  enabled: true\n  name_template: '{{ .ProjectName }}_{{ .Version }}_source_code'\nsboms:\n  - id: source\n    artifacts: source\n    documents:\n      - \"{{ .ProjectName }}_{{ .Version }}_sbom.spdx.json\"\nrelease:\n  extra_files:\n    - glob: output/crd-schemas.tar.gz\n    - glob: output/manifests.tar.gz\n    - glob: output/install.yaml\nchecksum:\n  extra_files:\n    - glob: output/crd-schemas.tar.gz\n    - glob: output/manifests.tar.gz\n    - glob: output/install.yaml\nsigns:\n  - cmd: cosign\n    env:\n      - COSIGN_EXPERIMENTAL=1\n    certificate: '${artifact}.pem'\n    args:\n      - sign-blob\n      - \"--yes\"\n      - '--output-certificate=${certificate}'\n      - '--output-signature=${signature}'\n      - '${artifact}'\n    artifacts: checksum\n    output: true\nbrews:\n  - name: flux\n    repository:\n      owner: fluxcd\n      name: homebrew-tap\n      token: \"{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}\"\n    directory: Formula\n    homepage: \"https://fluxcd.io/\"\n    description: \"Flux CLI\"\n    install: |\n      bin.install \"flux\"\n\n      generate_completions_from_executable(bin/\"flux\", \"completion\")\n    test: |\n      system \"#{bin}/flux --version\"\ndockers:\n- image_templates:\n    - 'fluxcd/flux-cli:{{ .Tag }}-amd64'\n    - 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-amd64'\n  dockerfile: Dockerfile\n  use: buildx\n  goos: linux\n  goarch: amd64\n  build_flag_templates:\n   - \"--pull\"\n   - \"--build-arg=ARCH=linux/amd64\"\n   - \"--label=org.opencontainers.image.created={{ .Date }}\"\n   - \"--label=org.opencontainers.image.name={{ .ProjectName }}\"\n   - \"--label=org.opencontainers.image.revision={{ .FullCommit }}\"\n   - \"--label=org.opencontainers.image.version={{ .Version }}\"\n   - \"--label=org.opencontainers.image.source={{ .GitURL }}\"\n   - \"--platform=linux/amd64\"\n- image_templates:\n    - 'fluxcd/flux-cli:{{ .Tag }}-arm64'\n    - 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm64'\n  dockerfile: Dockerfile\n  use: buildx\n  goos: linux\n  goarch: arm64\n  build_flag_templates:\n    - \"--pull\"\n    - \"--build-arg=ARCH=linux/arm64\"\n    - \"--label=org.opencontainers.image.created={{ .Date }}\"\n    - \"--label=org.opencontainers.image.name={{ .ProjectName }}\"\n    - \"--label=org.opencontainers.image.revision={{ .FullCommit }}\"\n    - \"--label=org.opencontainers.image.version={{ .Version }}\"\n    - \"--label=org.opencontainers.image.source={{ .GitURL }}\"\n    - \"--platform=linux/arm64\"\n- image_templates:\n    - 'fluxcd/flux-cli:{{ .Tag }}-arm'\n    - 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm'\n  dockerfile: Dockerfile\n  use: buildx\n  goos: linux\n  goarch: arm\n  goarm: 7\n  build_flag_templates:\n    - \"--pull\"\n    - \"--build-arg=ARCH=linux/arm\"\n    - \"--label=org.opencontainers.image.created={{ .Date }}\"\n    - \"--label=org.opencontainers.image.name={{ .ProjectName }}\"\n    - \"--label=org.opencontainers.image.revision={{ .FullCommit }}\"\n    - \"--label=org.opencontainers.image.version={{ .Version }}\"\n    - \"--label=org.opencontainers.image.source={{ .GitURL }}\"\n    - \"--platform=linux/arm/v7\"\ndocker_manifests:\n- name_template: 'fluxcd/flux-cli:{{ .Tag }}'\n  image_templates:\n    - 'fluxcd/flux-cli:{{ .Tag }}-amd64'\n    - 'fluxcd/flux-cli:{{ .Tag }}-arm64'\n    - 'fluxcd/flux-cli:{{ .Tag }}-arm'\n- name_template: 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}'\n  image_templates:\n    - 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-amd64'\n    - 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm64'\n    - 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm'\ndocker_signs:\n  - cmd: cosign\n    env:\n      - COSIGN_EXPERIMENTAL=1\n    args:\n      - sign\n      - \"--yes\"\n      - '${artifact}'\n    artifacts: all\n    output: true\n"
  },
  {
    "path": ".scorecard.yml",
    "content": "annotations:\n  - checks:\n      - dangerous-workflow\n    reasons:\n      - reason: not-applicable # This workflow does not run untrusted code, the bot will only backport a code if the a PR was approved and merged into main.\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "## Code of Conduct\n\nFluxCD toolkit follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nFlux is [Apache 2.0 licensed](https://github.com/fluxcd/flux2/blob/main/LICENSE) and\naccepts contributions via GitHub pull requests. This document outlines\nsome of the conventions on to make it easier to get your contribution\naccepted.\n\nWe gratefully welcome improvements to issues and documentation as well as to\ncode.\n\n## Certificate of Origin\n\nBy contributing to this project you agree to the Developer Certificate of\nOrigin (DCO). This document was created by the Linux Kernel community and is a\nsimple statement that you, as a contributor, have the legal right to make the\ncontribution.\n\nWe require all commits to be signed. By signing off with your signature, you\ncertify that you wrote the patch or otherwise have the right to contribute the\nmaterial by the rules of the [DCO](DCO):\n\n`Signed-off-by: Jane Doe <jane.doe@example.com>`\n\nThe signature must contain your real name\n(sorry, no pseudonyms or anonymous contributions)\nIf your `user.name` and `user.email` are configured in your Git config,\nyou can sign your commit automatically with `git commit -s`.\n\n## Communications\n\nFor realtime communications we use Slack: To join the conversation, simply\njoin the [CNCF](https://slack.cncf.io/) Slack workspace and use the\n[#flux-contributors](https://cloud-native.slack.com/messages/flux-contributors/) channel.\n\nTo discuss ideas and specifications we use [Github\nDiscussions](https://github.com/fluxcd/flux2/discussions).\n\nFor announcements we use a mailing list as well. Simply subscribe to\n[flux-dev on cncf.io](https://lists.cncf.io/g/cncf-flux-dev)\nto join the conversation (there you can also add calendar invites\nto your Google calendar for our [Flux\nmeeting](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view)).\n\n## Understanding Flux and the GitOps Toolkit\n\nIf you are entirely new to Flux and the GitOps Toolkit,\nyou might want to take a look at the [introductory talk and demo](https://www.youtube.com/watch?v=qQBtSkgl7tI).\n\nThis project is composed of:\n\n- [flux2](https://github.com/fluxcd/flux2): The Flux CLI\n- [source-controller](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources (Git, OCI and Helm repositories, S3-compatible Buckets)\n- [source-watcher](https://github.com/fluxcd/source-watcher): Kubernetes operator for advanced source composition and decomposition patterns\n- [kustomize-controller](https://github.com/fluxcd/kustomize-controller): Kubernetes operator for building GitOps pipelines with Kustomize\n- [helm-controller](https://github.com/fluxcd/helm-controller): Kubernetes operator for building GitOps pipelines with Helm\n- [notification-controller](https://github.com/fluxcd/notification-controller): Kubernetes operator for handling inbound and outbound events\n- [image-reflector-controller](https://github.com/fluxcd/image-reflector-controller): Kubernetes operator for scanning container registries\n- [image-automation-controller](https://github.com/fluxcd/image-automation-controller): Kubernetes operator for patches container image tags in Git\n\n### Understanding the code\n\nTo get started with developing controllers, you might want to review\n[our guide](https://fluxcd.io/flux/gitops-toolkit/source-watcher/) which\nwalks you through writing a short and concise controller that watches out\nfor source changes.\n\n## How to run the test suite\n\nPrerequisites:\n\n* go >= 1.26\n* kubectl >= 1.33\n* kustomize >= 5.0\n\nInstall the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with:\n\n```bash\nmake install-envtest\n```\n\nThen you can run the unit tests with:\n\n```bash\nmake test\n```\n\nAfter [installing Kubernetes kind](https://kind.sigs.k8s.io/docs/user/quick-start#installation) on your machine,\ncreate a cluster for testing with:\n\n```bash\nmake setup-kind\n```\n\nThen you can run the end-to-end tests with:\n\n```bash\nmake e2e\n```\n\nWhen the output of the Flux CLI changes, to automatically update the golden\nfiles used in the test, pass `-update` flag to the test as:\n\n```bash\nmake e2e TEST_ARGS=\"-update\"\n```\n\nSince not all packages use golden files for testing, `-update` argument must be\npassed only for the packages that use golden files. Use the variables\n`TEST_PKG_PATH` for unit tests and `E2E_TEST_PKG_PATH` for e2e tests, to set the\npath of the target test package:\n\n```bash\n# Unit test\nmake test TEST_PKG_PATH=\"./cmd/flux\" TEST_ARGS=\"-update\"\n# e2e test\nmake e2e E2E_TEST_PKG_PATH=\"./cmd/flux\" TEST_ARGS=\"-update\"\n```\n\nTeardown the e2e environment with:\n\n```bash\nmake cleanup-kind\n```\n\n## Acceptance policy\n\nThese things will make a PR more likely to be accepted:\n\n- a well-described requirement\n- tests for new code\n- tests for old code!\n- new code and tests follow the conventions in old code and tests\n- a good commit message (see below)\n- all code must abide [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)\n- names should abide [What's in a name](https://talks.golang.org/2014/names.slide#1)\n- code must build on both Linux and Darwin, via plain `go build`\n- code should have appropriate test coverage and tests should be written\n  to work with `go test`\n\nIn general, we will merge a PR once one maintainer has endorsed it.\nFor substantial changes, more people may become involved, and you might\nget asked to resubmit the PR or divide the changes into more than one PR.\n\n### Format of the Commit Message\n\nFor the GitOps Toolkit controllers we prefer the following rules for good commit messages:\n\n- Limit the subject to 50 characters and write as the continuation\n  of the sentence \"If applied, this commit will ...\"\n- Explain what and why in the body, if more than a trivial change;\n  wrap it at 72 characters.\n\nThe [following article](https://chris.beams.io/posts/git-commit/#seven-rules)\nhas some more helpful advice on documenting your work.\n"
  },
  {
    "path": "DCO",
    "content": "Developer Certificate of Origin\nVersion 1.1\n\nCopyright (C) 2004, 2006 The Linux Foundation and its contributors.\n660 York Street, Suite 102,\nSan Francisco, CA 94110 USA\n\nEveryone is permitted to copy and distribute verbatim copies of this\nlicense document, but changing it is not allowed.\n\n\nDeveloper's Certificate of Origin 1.1\n\nBy making a contribution to this project, I certify that:\n\n(a) The contribution was created in whole or in part by me and I\n    have the right to submit it under the open source license\n    indicated in the file; or\n\n(b) The contribution is based upon previous work that, to the best\n    of my knowledge, is covered under an appropriate open source\n    license and I have the right under that license to submit that\n    work with modifications, whether created in whole or in part\n    by me, under the same open source license (unless I am\n    permitted to submit under a different license), as indicated\n    in the file; or\n\n(c) The contribution was provided directly to me by some other\n    person who certified (a), (b) or (c) and I have not modified\n    it.\n\n(d) I understand and agree that this project and the contribution\n    are public and that a record of the contribution (including all\n    personal information I submit with it, including my sign-off) is\n    maintained indefinitely and may be redistributed consistent with\n    this project or the open source license(s) involved.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM alpine:3.23 AS builder\n\nRUN apk add --no-cache ca-certificates curl\n\nARG ARCH=linux/amd64\nARG KUBECTL_VER=1.35.0\n\nRUN curl -sL https://dl.k8s.io/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \\\n    -o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl\n\nRUN kubectl version --client=true\n\nFROM alpine:3.23 AS flux-cli\n\nRUN apk add --no-cache ca-certificates\n\nCOPY --from=builder /usr/local/bin/kubectl /usr/local/bin/\nCOPY --chmod=755 flux /usr/local/bin/\n\nUSER 65534:65534\nENTRYPOINT [ \"flux\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "MAINTAINERS",
    "content": "The maintainers are generally available in Slack at\nhttps://cloud-native.slack.com in #flux (https://cloud-native.slack.com/messages/CLAJ40HV3)\n(obtain an invitation at https://slack.cncf.io/).\n\nThe Flux2 maintainers team is identical with the core maintainers of the project\nas listed in\n\n    https://github.com/fluxcd/community/blob/main/CORE-MAINTAINERS\n"
  },
  {
    "path": "Makefile",
    "content": "VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | head -n 1 | tr -d '\"')\nDEV_VERSION?=0.0.0-$(shell git rev-parse --abbrev-ref HEAD)-$(shell git rev-parse --short HEAD)-$(shell date +%s)\nEMBEDDED_MANIFESTS_TARGET=cmd/flux/.manifests.done\nTEST_KUBECONFIG?=/tmp/flux-e2e-test-kubeconfig\n# Architecture to use envtest with\nENVTEST_ARCH ?= amd64\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\nrwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2)) $(filter $(subst *,%,$(2)),$(d)))\n\nall: test build\n\ntidy:\n\tgo mod tidy -compat=1.26\n\tcd tests/integration && go mod tidy -compat=1.26\n\nfmt:\n\tgo fmt ./...\n\nvet:\n\tgo vet ./...\n\nsetup-kind:\n\tkind create cluster --name=flux-e2e-test --kubeconfig=$(TEST_KUBECONFIG) --config=.github/kind/config.yaml\n\tkubectl --kubeconfig=$(TEST_KUBECONFIG) apply -f https://docs.projectcalico.org/v3.16/manifests/calico.yaml\n\tkubectl --kubeconfig=$(TEST_KUBECONFIG) -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true\n\ncleanup-kind:\n\tkind delete cluster --name=flux-e2e-test\n\trm $(TEST_KUBECONFIG)\n\nKUBEBUILDER_ASSETS?=\"$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)\"\nTEST_PKG_PATH=\"./...\"\ntest: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet install-envtest\n\tKUBEBUILDER_ASSETS=\"$(KUBEBUILDER_ASSETS)\" go test $(TEST_PKG_PATH) -coverprofile cover.out --tags=unit $(TEST_ARGS)\n\nE2E_TEST_PKG_PATH=\"./cmd/flux/...\"\ne2e: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet\n\tTEST_KUBECONFIG=$(TEST_KUBECONFIG) go test $(E2E_TEST_PKG_PATH) -coverprofile e2e.cover.out --tags=e2e -v -failfast $(TEST_ARGS)\n\ntest-with-kind: install-envtest\n\tmake setup-kind\n\tmake e2e\n\tmake cleanup-kind\n\n$(EMBEDDED_MANIFESTS_TARGET): $(call rwildcard,manifests/,*.yaml *.json)\n\t./manifests/scripts/bundle.sh\n\ttouch $@\n\nbuild: $(EMBEDDED_MANIFESTS_TARGET)\n\tCGO_ENABLED=0 go build -ldflags=\"-s -w -X main.VERSION=$(VERSION)\" -o ./bin/flux ./cmd/flux\n\nbuild-dev: $(EMBEDDED_MANIFESTS_TARGET)\n\tCGO_ENABLED=0 go build -ldflags=\"-s -w -X main.VERSION=$(DEV_VERSION)\" -o ./bin/flux ./cmd/flux\n\n.PHONY: install\ninstall:\n\tCGO_ENABLED=0 go install ./cmd/flux\n\ninstall-dev:\n\tCGO_ENABLED=0 go build -o /usr/local/bin ./cmd/flux\n\nsetup-bootstrap-patch:\n\tgo run ./tests/bootstrap/main.go\n\nsetup-image-automation:\n\tcd tests/image-automation && go run main.go\n\nENVTEST_ASSETS_DIR=$(shell pwd)/testbin\nENVTEST_KUBERNETES_VERSION?=latest\ninstall-envtest: setup-envtest\n\tmkdir -p ${ENVTEST_ASSETS_DIR}\n\t$(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR)\n\nENVTEST = $(shell pwd)/bin/setup-envtest\n.PHONY: envtest\nsetup-envtest: ## Download envtest-setup locally if necessary.\n\t$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)\n\n# go-install-tool will 'go install' any package $2 and install it to $1.\nPROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))\ndefine go-install-tool\n@[ -f $(1) ] || { \\\nset -e ;\\\nTMP_DIR=$$(mktemp -d) ;\\\ncd $$TMP_DIR ;\\\ngo mod init tmp ;\\\necho \"Downloading $(2)\" ;\\\nGOBIN=$(PROJECT_DIR)/bin go install $(2) ;\\\nrm -rf $$TMP_DIR ;\\\n}\nendef\n"
  },
  {
    "path": "README.md",
    "content": "# Flux version 2\n\n[![release](https://img.shields.io/github/release/fluxcd/flux2/all.svg)](https://github.com/fluxcd/flux2/releases)\n[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4782/badge)](https://bestpractices.coreinfrastructure.org/projects/4782)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2/badge)](https://scorecard.dev/viewer/?uri=github.com/fluxcd/flux2)\n[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2?ref=badge_shield)\n[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/flux2)](https://artifacthub.io/packages/helm/fluxcd-community/flux2)\n[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://fluxcd.io/flux/security/slsa-assessment)\n\nFlux is a tool for keeping Kubernetes clusters in sync with sources of\nconfiguration (like Git repositories and OCI artifacts),\nand automating updates to configuration when there is new code to deploy.\n\nFlux version 2 (\"v2\") is built from the ground up to use Kubernetes'\nAPI extension system, and to integrate with Prometheus and other core\ncomponents of the Kubernetes ecosystem. In version 2, Flux supports\nmulti-tenancy and support for syncing an arbitrary number of Git\nrepositories, among other long-requested features.\n\nFlux v2 is constructed with the [GitOps Toolkit](#gitops-toolkit), a\nset of composable APIs and specialized tools for building Continuous\nDelivery on top of Kubernetes.\n\nFlux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) graduated project, used in\nproduction by various [organisations](https://fluxcd.io/adopters) and [cloud providers](https://fluxcd.io/ecosystem).\n\n## Quickstart and documentation\n\nTo get started check out this [guide](https://fluxcd.io/flux/get-started/)\non how to bootstrap Flux on Kubernetes and deploy a sample application in a GitOps manner.\n\nFor more comprehensive documentation, see the following guides:\n- [Ways of structuring your repositories](https://fluxcd.io/flux/guides/repository-structure/)\n- [Manage Helm Releases](https://fluxcd.io/flux/guides/helmreleases/)\n- [Automate image updates to Git](https://fluxcd.io/flux/guides/image-update/)  \n- [Manage Kubernetes secrets with Flux and SOPS](https://fluxcd.io/flux/guides/mozilla-sops/)  \n\nIf you need help, please refer to our **[Support page](https://fluxcd.io/support/)**.\n\n## GitOps Toolkit\n\nThe GitOps Toolkit is the set of APIs and controllers that make up the\nruntime for Flux v2. The APIs comprise Kubernetes custom resources,\nwhich can be created and updated by a cluster user, or by other\nautomation tooling.\n\n![overview](https://raw.githubusercontent.com/fluxcd/flux2/main/docs/diagrams/fluxcd-controllers.png)\n\nYou can use the toolkit to extend Flux, or to build your own systems\nfor continuous delivery -- see [the developer\nguides](https://fluxcd.io/flux/gitops-toolkit/source-watcher/).\n\n### Components\n\n- [Source Controllers](https://fluxcd.io/flux/components/source/)\n    - [GitRepository CRD](https://fluxcd.io/flux/components/source/gitrepositories/)\n    - [OCIRepository CRD](https://fluxcd.io/flux/components/source/ocirepositories/)\n    - [HelmRepository CRD](https://fluxcd.io/flux/components/source/helmrepositories/)\n    - [HelmChart CRD](https://fluxcd.io/flux/components/source/helmcharts/)\n    - [Bucket CRD](https://fluxcd.io/flux/components/source/buckets/)\n    - [ExternalArtifact CRD](https://fluxcd.io/flux/components/source/externalartifacts/)\n    - [ArtifactGenerator CRD](https://fluxcd.io/flux/components/source/artifactgenerators/)\n- [Kustomize Controller](https://fluxcd.io/flux/components/kustomize/)\n    - [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomizations/)\n- [Helm Controller](https://fluxcd.io/flux/components/helm/)\n    - [HelmRelease CRD](https://fluxcd.io/flux/components/helm/helmreleases/)\n- [Notification Controller](https://fluxcd.io/flux/components/notification/)\n    - [Provider CRD](https://fluxcd.io/flux/components/notification/providers/)\n    - [Alert CRD](https://fluxcd.io/flux/components/notification/alerts/)\n    - [Receiver CRD](https://fluxcd.io/flux/components/notification/receivers/)\n- [Image Automation Controllers](https://fluxcd.io/flux/components/image/)\n  - [ImageRepository CRD](https://fluxcd.io/flux/components/image/imagerepositories/)\n  - [ImagePolicy CRD](https://fluxcd.io/flux/components/image/imagepolicies/)\n  - [ImageUpdateAutomation CRD](https://fluxcd.io/flux/components/image/imageupdateautomations/)\n\n## Community\n\nNeed help or want to contribute? Please see the links below. The Flux project is always looking for\nnew contributors and there are a multitude of ways to get involved.\n\n- Getting Started?\n    - Look at our [Get Started guide](https://fluxcd.io/flux/get-started/) and give us feedback\n- Need help?\n    - First: Ask questions on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions).\n    - Second: Talk to us in the #flux channel on [CNCF Slack](https://slack.cncf.io/).\n    - Please follow our [Support Guidelines](https://fluxcd.io/support/)\n      (in short: be nice, be respectful of volunteers' time, understand that maintainers and\n      contributors cannot respond to all DMs, and keep discussions in the public #flux channel as much as possible).\n- Have feature proposals or want to contribute?\n    - Propose features on our [GitHub Discussions page](https://github.com/fluxcd/flux2/discussions).\n    - Join our upcoming dev meetings ([meeting access and agenda](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view)).\n    - [Join the flux-dev mailing list](https://lists.cncf.io/g/cncf-flux-dev).\n    - Check out [how to contribute](CONTRIBUTING.md) to the project.\n    - Check out the [project roadmap](https://fluxcd.io/roadmap/).\n\n### Events\n\nCheck out our **[events calendar](https://fluxcd.io/#calendar)**,\nboth with upcoming talks, events and meetings you can attend.\nOr view the **[resources section](https://fluxcd.io/resources)**\nwith past events videos you can watch.\n\nWe look forward to seeing you with us!\n"
  },
  {
    "path": "action/README.md",
    "content": "# Flux GitHub Action\n\nTo install the latest Flux CLI on Linux, macOS or Windows GitHub runners:\n\n```yaml\nsteps:\n  - name: Setup Flux CLI\n    uses: fluxcd/flux2/action@main\n    with:\n      version: 'latest'\n  - name: Run Flux CLI\n    run: flux version --client\n```\n\nThe Flux GitHub Action can be used to automate various tasks in CI, such as:\n\n- [Automate Flux upgrades on clusters via Pull Requests](https://fluxcd.io/flux/flux-gh-action/#automate-flux-updates)\n- [Push Kubernetes manifests to container registries](https://fluxcd.io/flux/flux-gh-action/#push-kubernetes-manifests-to-container-registries)\n- [Run end-to-end testing with Flux and Kubernetes Kind](https://fluxcd.io/flux/flux-gh-action/#end-to-end-testing)\n\nFor more information, please see the [Flux GitHub Action documentation](https://fluxcd.io/flux/flux-gh-action/).\n\n"
  },
  {
    "path": "action/action.yml",
    "content": "name: Setup Flux CLI\ndescription: A GitHub Action for installing the Flux CLI\nauthor: Flux project\nbranding:\n  color: blue\n  icon: command\ninputs:\n  version:\n    description: \"Flux version e.g. 2.0.0 (defaults to latest stable release)\"\n    required: false\n  arch:\n    description: \"arch can be amd64, arm64 or arm\"\n    required: false\n    deprecationMessage: \"No longer required, action will now detect runner arch.\"\n  bindir:\n    description: \"Alternative location for the Flux binary, defaults to path relative to $RUNNER_TOOL_CACHE.\"\n    required: false\n  token:\n    description: \"Token used to authenticate against the GitHub.com API.\"\n    required: false\nruns:\n  using: composite\n  steps:\n    - name: \"Download the binary to the runner's cache dir\"\n      shell: bash\n      env:\n        VERSION: \"${{ inputs.version }}\"\n        FLUX_TOOL_DIR: \"${{ inputs.bindir }}\"\n        TOKEN: \"${{ inputs.token }}\"\n      run: |\n        if [[ -z \"$VERSION\" ]] || [[ \"$VERSION\" = \"latest\" ]]; then\n          if [[ \"${TOKEN}\" != '' ]]; then\n            VERSION=$(curl -fsSL -H \"Authorization: token ${TOKEN}\" https://api.github.com/repos/fluxcd/flux2/releases/latest | grep tag_name | cut -d '\"' -f 4)\n          else\n            VERSION=$(curl -w \"%{url_effective}\\n\" -IsSL https://github.com/fluxcd/flux2/releases/latest -o /dev/null | sed 's$^.*/$$')\n          fi\n        fi\n        if [[ -z \"$VERSION\" ]]; then\n          echo \"Unable to determine Flux CLI version\"\n          exit 1\n        fi\n        if [[ $VERSION = v* ]]; then\n          VERSION=\"${VERSION:1}\"\n        fi\n\n        OS=$(echo \"${RUNNER_OS}\" | tr '[:upper:]' '[:lower:]')\n        if [[ \"$OS\" == \"macos\" ]]; then\n          OS=\"darwin\"\n        fi\n\n        ARCH=$(echo \"${RUNNER_ARCH}\" | tr '[:upper:]' '[:lower:]')\n        if [[ \"$ARCH\" == \"x64\" ]]; then\n          ARCH=\"amd64\"\n        elif [[ \"$ARCH\" == \"x86\" ]]; then\n          ARCH=\"386\"\n        fi\n\n        FLUX_EXEC_FILE=\"flux\"\n        if [[ \"$OS\" == \"windows\" ]]; then\n            FLUX_EXEC_FILE=\"${FLUX_EXEC_FILE}.exe\"\n        fi\n\n        if [[ -z \"$FLUX_TOOL_DIR\" ]]; then\n          FLUX_TOOL_DIR=\"${RUNNER_TOOL_CACHE}/flux2/${VERSION}/${OS}/${ARCH}\"\n        fi\n        if [[ ! -x \"$FLUX_TOOL_DIR/FLUX_EXEC_FILE\" ]]; then\n          DL_DIR=\"$(mktemp -dt flux2-XXXXXX)\"\n          trap 'rm -rf $DL_DIR' EXIT\n\n          echo \"Downloading flux ${VERSION} for ${OS}/${ARCH}\"\n          FLUX_TARGET_FILE=\"flux_${VERSION}_${OS}_${ARCH}.tar.gz\"\n          if [[ \"$OS\" == \"windows\" ]]; then\n            FLUX_TARGET_FILE=\"flux_${VERSION}_${OS}_${ARCH}.zip\"\n          fi\n\n          FLUX_CHECKSUMS_FILE=\"flux_${VERSION}_checksums.txt\"\n\n          FLUX_DOWNLOAD_URL=\"https://github.com/fluxcd/flux2/releases/download/v${VERSION}/\"\n\n          MAX_RETRIES=5\n          RETRY_DELAY=5\n          \n          for i in $(seq 1 $MAX_RETRIES); do\n            echo \"Downloading flux binary (attempt $i/$MAX_RETRIES)\"\n            if curl -fsSL -o \"$DL_DIR/$FLUX_TARGET_FILE\" \"$FLUX_DOWNLOAD_URL/$FLUX_TARGET_FILE\"; then\n              break\n            fi\n            if [ $i -lt $MAX_RETRIES ]; then\n              echo \"Download failed, retrying in ${RETRY_DELAY} seconds...\"\n              sleep $RETRY_DELAY\n            else\n              echo \"Failed to download flux binary after $MAX_RETRIES attempts\"\n              exit 1\n            fi\n          done\n          \n          for i in $(seq 1 $MAX_RETRIES); do\n            echo \"Downloading checksums file (attempt $i/$MAX_RETRIES)\"\n            if curl -fsSL -o \"$DL_DIR/$FLUX_CHECKSUMS_FILE\" \"$FLUX_DOWNLOAD_URL/$FLUX_CHECKSUMS_FILE\"; then\n              break\n            fi\n            if [ $i -lt $MAX_RETRIES ]; then\n              echo \"Download failed, retrying in ${RETRY_DELAY} seconds...\"\n              sleep $RETRY_DELAY\n            else\n              echo \"Failed to download checksums file after $MAX_RETRIES attempts\"\n              exit 1\n            fi\n          done\n        \n          echo \"Verifying checksum\"\n          sum=\"\"\n          if command -v openssl > /dev/null; then\n            sum=$(openssl sha256 \"$DL_DIR/$FLUX_TARGET_FILE\" | awk '{print $2}')\n          elif command -v sha256sum > /dev/null; then\n            sum=$(sha256sum \"$DL_DIR/$FLUX_TARGET_FILE\" | awk '{print $1}')\n          fi\n\n          if [[ -z \"$sum\" ]]; then\n            echo \"Neither openssl nor sha256sum found. Cannot calculate checksum.\"\n            exit 1\n          fi\n\n          expected_sum=$(grep \" $FLUX_TARGET_FILE\\$\" \"$DL_DIR/$FLUX_CHECKSUMS_FILE\" | awk '{print $1}')\n          if [ \"$sum\" != \"$expected_sum\" ]; then\n            echo \"SHA sum of ${FLUX_TARGET_FILE} does not match. Aborting.\"\n            exit 1\n          fi\n\n          echo \"Installing flux to ${FLUX_TOOL_DIR}\"\n          mkdir -p \"$FLUX_TOOL_DIR\"\n        \n          if [[ \"$OS\" == \"windows\" ]]; then\n            unzip \"$DL_DIR/$FLUX_TARGET_FILE\" \"$FLUX_EXEC_FILE\" -d \"$FLUX_TOOL_DIR\"\n          else\n            tar xzf \"$DL_DIR/$FLUX_TARGET_FILE\" -C \"$FLUX_TOOL_DIR\" $FLUX_EXEC_FILE\n          fi\n\n          chmod +x \"$FLUX_TOOL_DIR/$FLUX_EXEC_FILE\"\n        fi\n\n        echo \"Adding flux to path\"\n        echo \"$FLUX_TOOL_DIR\" >> \"$GITHUB_PATH\"\n\n    - name: \"Print installed flux version\"\n      shell: bash\n      run: |\n        flux -v\n"
  },
  {
    "path": "cmd/flux/alert.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\n// notificationv1.Alert\n\nvar alertType = apiType{\n\tkind:         notificationv1.AlertKind,\n\thumanKind:    \"alert\",\n\tgroupVersion: notificationv1.GroupVersion,\n}\n\ntype alertAdapter struct {\n\t*notificationv1.Alert\n}\n\nfunc (a alertAdapter) asClientObject() client.Object {\n\treturn a.Alert\n}\n\nfunc (a alertAdapter) deepCopyClientObject() client.Object {\n\treturn a.Alert.DeepCopy()\n}\n\n// notificationv1.Alert\n\ntype alertListAdapter struct {\n\t*notificationv1.AlertList\n}\n\nfunc (a alertListAdapter) asClientList() client.ObjectList {\n\treturn a.AlertList\n}\n\nfunc (a alertListAdapter) len() int {\n\treturn len(a.AlertList.Items)\n}\n"
  },
  {
    "path": "cmd/flux/alert_provider.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\n// notificationv1.Provider\n\nvar alertProviderType = apiType{\n\tkind:         notificationv1.ProviderKind,\n\thumanKind:    \"alert provider\",\n\tgroupVersion: notificationv1.GroupVersion,\n}\n\ntype alertProviderAdapter struct {\n\t*notificationv1.Provider\n}\n\nfunc (a alertProviderAdapter) asClientObject() client.Object {\n\treturn a.Provider\n}\n\nfunc (a alertProviderAdapter) deepCopyClientObject() client.Object {\n\treturn a.Provider.DeepCopy()\n}\n\n// notificationv1.Provider\n\ntype alertProviderListAdapter struct {\n\t*notificationv1.ProviderList\n}\n\nfunc (a alertProviderListAdapter) asClientList() client.ObjectList {\n\treturn a.ProviderList\n}\n\nfunc (a alertProviderListAdapter) len() int {\n\treturn len(a.ProviderList.Items)\n}\n"
  },
  {
    "path": "cmd/flux/artifact.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tswapi \"github.com/fluxcd/source-watcher/api/v2/v1beta1\"\n)\n\n// swapi.ArtifactGenerator\n\nvar artifactGeneratorType = apiType{\n\tkind:         swapi.ArtifactGeneratorKind,\n\thumanKind:    \"artifactgenerator\",\n\tgroupVersion: swapi.GroupVersion,\n}\n\ntype artifactGeneratorAdapter struct {\n\t*swapi.ArtifactGenerator\n}\n\nfunc (h artifactGeneratorAdapter) asClientObject() client.Object {\n\treturn h.ArtifactGenerator\n}\n\nfunc (h artifactGeneratorAdapter) deepCopyClientObject() client.Object {\n\treturn h.ArtifactGenerator.DeepCopy()\n}\n\n// swapi.ArtifactGeneratorList\n\ntype artifactGeneratorListAdapter struct {\n\t*swapi.ArtifactGeneratorList\n}\n\nfunc (h artifactGeneratorListAdapter) asClientList() client.ObjectList {\n\treturn h.ArtifactGeneratorList\n}\n\nfunc (h artifactGeneratorListAdapter) len() int {\n\treturn len(h.ArtifactGeneratorList.Items)\n}\n"
  },
  {
    "path": "cmd/flux/bootstrap.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"crypto/elliptic\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fluxcd/pkg/git\"\n\t\"github.com/manifoldco/promptui\"\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n)\n\nvar bootstrapCmd = &cobra.Command{\n\tUse:   \"bootstrap\",\n\tShort: \"Deploy Flux on a cluster the GitOps way.\",\n\tLong: `The bootstrap sub-commands push the Flux manifests to a Git repository\nand deploy Flux on the cluster.`,\n}\n\ntype bootstrapFlags struct {\n\tversion  string\n\tlogLevel flags.LogLevel\n\n\tbranch            string\n\trecurseSubmodules bool\n\tmanifestsPath     string\n\n\tdefaultComponents  []string\n\textraComponents    []string\n\trequiredComponents []string\n\n\tregistry           string\n\tregistryCredential string\n\timagePullSecret    string\n\n\tsecretName           string\n\ttokenAuth            bool\n\tkeyAlgorithm         flags.PublicKeyAlgorithm\n\tkeyRSABits           flags.RSAKeyBits\n\tkeyECDSACurve        flags.ECDSACurve\n\tsshHostname          string\n\tcaFile               string\n\tprivateKeyFile       string\n\tsshHostKeyAlgorithms []string\n\n\twatchAllNamespaces bool\n\tnetworkPolicy      bool\n\tclusterDomain      string\n\ttolerationKeys     []string\n\n\tauthorName  string\n\tauthorEmail string\n\n\tgpgKeyRingPath string\n\tgpgPassphrase  string\n\tgpgKeyID       string\n\n\tforce bool\n\n\tcommitMessageAppendix string\n}\n\nconst (\n\tbootstrapDefaultBranch = \"main\"\n)\n\nvar bootstrapArgs = NewBootstrapFlags()\n\nfunc init() {\n\tbootstrapCmd.PersistentFlags().StringVarP(&bootstrapArgs.version, \"version\", \"v\", \"\",\n\t\t\"toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases\")\n\n\tbootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, \"components\", rootArgs.defaults.Components,\n\t\t\"list of components, accepts comma-separated values\")\n\tbootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, \"components-extra\", nil,\n\t\t\"list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller,source-watcher'\")\n\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, \"registry\", \"ghcr.io/fluxcd\",\n\t\t\"container registry where the Flux controller images are published\")\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registryCredential, \"registry-creds\", \"\",\n\t\t\"container registry credentials in the format 'user:password', requires --image-pull-secret to be set\")\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.imagePullSecret, \"image-pull-secret\", \"\",\n\t\t\"Kubernetes secret name used for pulling the controller images from a private registry\")\n\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.branch, \"branch\", bootstrapDefaultBranch, \"Git branch\")\n\tbootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.recurseSubmodules, \"recurse-submodules\", false,\n\t\t\"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces\")\n\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.manifestsPath, \"manifests\", \"\", \"path to the manifest directory\")\n\n\tbootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.watchAllNamespaces, \"watch-all-namespaces\", true,\n\t\t\"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the Flux controllers are installed\")\n\tbootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.networkPolicy, \"network-policy\", true,\n\t\t\"setup Kubernetes network policies to deny ingress access to the Flux controllers from other namespaces\")\n\tbootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.tokenAuth, \"token-auth\", false,\n\t\t\"when enabled, the personal access token will be used instead of the SSH deploy key\")\n\tbootstrapCmd.PersistentFlags().Var(&bootstrapArgs.logLevel, \"log-level\", bootstrapArgs.logLevel.Description())\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.clusterDomain, \"cluster-domain\", rootArgs.defaults.ClusterDomain, \"internal cluster domain\")\n\tbootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.tolerationKeys, \"toleration-keys\", nil,\n\t\t\"list of toleration keys used to schedule the controller pods onto nodes with matching taints\")\n\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.secretName, \"secret-name\", rootArgs.defaults.Namespace, \"name of the secret the sync credentials can be found in or stored to\")\n\tbootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyAlgorithm, \"ssh-key-algorithm\", bootstrapArgs.keyAlgorithm.Description())\n\tbootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyRSABits, \"ssh-rsa-bits\", bootstrapArgs.keyRSABits.Description())\n\tbootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.sshHostKeyAlgorithms, \"ssh-hostkey-algos\", nil, \"list of host key algorithms to be used by the CLI for SSH connections\")\n\tbootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyECDSACurve, \"ssh-ecdsa-curve\", bootstrapArgs.keyECDSACurve.Description())\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.sshHostname, \"ssh-hostname\", \"\", \"SSH hostname, to be used when the SSH host differs from the HTTPS one\")\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.caFile, \"ca-file\", \"\", \"path to TLS CA file used for validating self-signed certificates\")\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.privateKeyFile, \"private-key-file\", \"\", \"path to a private key file used for authenticating to the Git SSH server\")\n\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorName, \"author-name\", \"Flux\", \"author name for Git commits\")\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorEmail, \"author-email\", \"\", \"author email for Git commits\")\n\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgKeyRingPath, \"gpg-key-ring\", \"\", \"path to GPG key ring for signing commits\")\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgPassphrase, \"gpg-passphrase\", \"\", \"passphrase for decrypting GPG private key\")\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgKeyID, \"gpg-key-id\", \"\", \"key id for selecting a particular key\")\n\n\tbootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, \"commit-message-appendix\", \"\", \"string to add to the commit messages, e.g. '[ci skip]'\")\n\n\tbootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.force, \"force\", false, \"override existing Flux installation if it's managed by a different tool such as Helm\")\n\tbootstrapCmd.PersistentFlags().MarkHidden(\"manifests\")\n\n\trootCmd.AddCommand(bootstrapCmd)\n}\n\nfunc NewBootstrapFlags() bootstrapFlags {\n\treturn bootstrapFlags{\n\t\tlogLevel:           flags.LogLevel(rootArgs.defaults.LogLevel),\n\t\trequiredComponents: []string{\"source-controller\", \"kustomize-controller\"},\n\t\tkeyAlgorithm:       flags.PublicKeyAlgorithm(sourcesecret.ECDSAPrivateKeyAlgorithm),\n\t\tkeyRSABits:         2048,\n\t\tkeyECDSACurve:      flags.ECDSACurve{Curve: elliptic.P384()},\n\t}\n}\n\nfunc bootstrapComponents() []string {\n\treturn append(bootstrapArgs.defaultComponents, bootstrapArgs.extraComponents...)\n}\n\nfunc buildEmbeddedManifestBase() (string, error) {\n\tif !isEmbeddedVersion(bootstrapArgs.version) {\n\t\treturn \"\", nil\n\t}\n\ttmpBaseDir, err := manifestgen.MkdirTempAbs(\"\", \"flux-manifests-\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif err := writeEmbeddedManifests(tmpBaseDir); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn tmpBaseDir, nil\n}\n\nfunc bootstrapValidate() error {\n\tcomponents := bootstrapComponents()\n\tfor _, component := range bootstrapArgs.requiredComponents {\n\t\tif !utils.ContainsItemString(components, component) {\n\t\t\treturn fmt.Errorf(\"component %s is required\", component)\n\t\t}\n\t}\n\n\tif err := utils.ValidateComponents(components); err != nil {\n\t\treturn err\n\t}\n\n\tif bootstrapArgs.registryCredential != \"\" && bootstrapArgs.imagePullSecret == \"\" {\n\t\treturn fmt.Errorf(\"--registry-creds requires --image-pull-secret to be set\")\n\t}\n\n\tif bootstrapArgs.registryCredential != \"\" && len(strings.Split(bootstrapArgs.registryCredential, \":\")) != 2 {\n\t\treturn fmt.Errorf(\"invalid --registry-creds format, expected 'user:password'\")\n\t}\n\n\tif len(bootstrapArgs.sshHostKeyAlgorithms) > 0 {\n\t\tgit.HostKeyAlgos = bootstrapArgs.sshHostKeyAlgorithms\n\t}\n\n\treturn nil\n}\n\nfunc mapTeamSlice(s []string, defaultPermission string) map[string]string {\n\tm := make(map[string]string, len(s))\n\tfor _, v := range s {\n\t\tm[v] = defaultPermission\n\t\tif s := strings.Split(v, \":\"); len(s) == 2 {\n\t\t\tm[s[0]] = s[1]\n\t\t}\n\t}\n\n\treturn m\n}\n\n// confirmBootstrap gets a confirmation for running bootstrap over an existing Flux installation.\n// It returns a nil error if Flux is not installed or the user confirms overriding an existing installation\nfunc confirmBootstrap(ctx context.Context, kubeClient client.Client) error {\n\tinstalled := true\n\tinfo, err := getFluxClusterInfo(ctx, kubeClient)\n\tif err != nil {\n\t\tif !errors.IsNotFound(err) {\n\t\t\treturn fmt.Errorf(\"cluster info unavailable: %w\", err)\n\t\t}\n\t\tinstalled = false\n\t}\n\n\tif installed {\n\t\terr = confirmFluxInstallOverride(info)\n\t\tif err != nil {\n\t\t\tif err == promptui.ErrAbort {\n\t\t\t\treturn fmt.Errorf(\"bootstrap cancelled\")\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/bootstrap_bitbucket_server.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/fluxcd/pkg/git\"\n\t\"github.com/fluxcd/pkg/git/gogit\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/bootstrap\"\n\t\"github.com/fluxcd/flux2/v2/pkg/bootstrap/provider\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync\"\n)\n\nvar bootstrapBServerCmd = &cobra.Command{\n\tUse:   \"bitbucket-server\",\n\tShort: \"Deploy Flux on a cluster connected to a Bitbucket Server repository\",\n\tLong: `The bootstrap bitbucket-server command creates the Bitbucket Server repository if it doesn't exists and\ncommits the Flux manifests to the master branch.\nThen it configures the target cluster to synchronize with the repository.\nIf the Flux components are present on the cluster,\nthe bootstrap command will perform an upgrade if needed.`,\n\tExample: `  # Create a Bitbucket Server API token and export it as an env var\n  export BITBUCKET_TOKEN=<my-token>\n\n  # Run bootstrap for a private repository using HTTPS token authentication\n  flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --hostname=<domain> --token-auth --path=clusters/my-cluster\n\n  # Run bootstrap for a private repository using SSH authentication\n  flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --hostname=<domain> --path=clusters/my-cluster\n\n  # Run bootstrap for a public repository on a personal account\n  flux bootstrap bitbucket-server --owner=<user> --repository=<repository name> --private=false --personal --hostname=<domain> --token-auth --path=clusters/my-cluster\n\n  # Run bootstrap for an existing repository with a branch named main\n  flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --branch=main --hostname=<domain> --token-auth --path=clusters/my-cluster`,\n\tRunE: bootstrapBServerCmdRun,\n}\n\nconst (\n\tbServerDefaultPermission = \"push\"\n\tbServerTokenEnvVar       = \"BITBUCKET_TOKEN\"\n)\n\ntype bServerFlags struct {\n\towner        string\n\trepository   string\n\tinterval     time.Duration\n\tpersonal     bool\n\tusername     string\n\tprivate      bool\n\thostname     string\n\tpath         flags.SafeRelativePath\n\tteams        []string\n\treadWriteKey bool\n\treconcile    bool\n}\n\nvar bServerArgs bServerFlags\n\nfunc init() {\n\tbootstrapBServerCmd.Flags().StringVar(&bServerArgs.owner, \"owner\", \"\", \"Bitbucket Server user or project name\")\n\tbootstrapBServerCmd.Flags().StringVar(&bServerArgs.repository, \"repository\", \"\", \"Bitbucket Server repository name\")\n\tbootstrapBServerCmd.Flags().StringSliceVar(&bServerArgs.teams, \"group\", []string{}, \"Bitbucket Server groups to be given write access (also accepts comma-separated values)\")\n\tbootstrapBServerCmd.Flags().BoolVar(&bServerArgs.personal, \"personal\", false, \"if true, the owner is assumed to be a Bitbucket Server user; otherwise a group\")\n\tbootstrapBServerCmd.Flags().StringVarP(&bServerArgs.username, \"username\", \"u\", \"git\", \"authentication username\")\n\tbootstrapBServerCmd.Flags().BoolVar(&bServerArgs.private, \"private\", true, \"if true, the repository is setup or configured as private\")\n\tbootstrapBServerCmd.Flags().DurationVar(&bServerArgs.interval, \"interval\", time.Minute, \"sync interval\")\n\tbootstrapBServerCmd.Flags().StringVar(&bServerArgs.hostname, \"hostname\", \"\", \"Bitbucket Server hostname\")\n\tbootstrapBServerCmd.Flags().Var(&bServerArgs.path, \"path\", \"path relative to the repository root, when specified the cluster sync will be scoped to this path\")\n\tbootstrapBServerCmd.Flags().BoolVar(&bServerArgs.readWriteKey, \"read-write-key\", false, \"if true, the deploy key is configured with read/write permissions\")\n\tbootstrapBServerCmd.Flags().BoolVar(&bServerArgs.reconcile, \"reconcile\", false, \"if true, the configured options are also reconciled if the repository already exists\")\n\n\tbootstrapCmd.AddCommand(bootstrapBServerCmd)\n}\n\nfunc bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {\n\tbitbucketToken := os.Getenv(bServerTokenEnvVar)\n\tif bitbucketToken == \"\" {\n\t\tvar err error\n\t\tbitbucketToken, err = readPasswordFromStdin(\"Please enter your Bitbucket personal access token (PAT): \")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not read token: %w\", err)\n\t\t}\n\t}\n\n\tif bServerArgs.hostname == \"\" {\n\t\treturn fmt.Errorf(\"invalid hostname %q\", bServerArgs.hostname)\n\t}\n\n\tif err := bootstrapValidate(); err != nil {\n\t\treturn err\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !bootstrapArgs.force {\n\t\terr = confirmBootstrap(ctx, kubeClient)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Manifest base\n\tif ver, err := getVersion(bootstrapArgs.version); err != nil {\n\t\treturn err\n\t} else {\n\t\tbootstrapArgs.version = ver\n\t}\n\tmanifestsBase, err := buildEmbeddedManifestBase()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(manifestsBase)\n\n\tuser := bServerArgs.username\n\tif bServerArgs.personal {\n\t\tuser = bServerArgs.owner\n\t}\n\n\tvar caBundle []byte\n\tif bootstrapArgs.caFile != \"\" {\n\t\tvar err error\n\t\tcaBundle, err = os.ReadFile(bootstrapArgs.caFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to read TLS CA file: %w\", err)\n\t\t}\n\t}\n\n\t// Build Bitbucket Server provider\n\tproviderCfg := provider.Config{\n\t\tProvider: provider.GitProviderStash,\n\t\tHostname: bServerArgs.hostname,\n\t\tUsername: user,\n\t\tToken:    bitbucketToken,\n\t\tCaBundle: caBundle,\n\t}\n\n\tproviderClient, err := provider.BuildGitProvider(providerCfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Lazy go-git repository\n\ttmpDir, err := manifestgen.MkdirTempAbs(\"\", \"flux-bootstrap-\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create temporary working dir: %w\", err)\n\t}\n\tdefer os.RemoveAll(tmpDir)\n\n\tclientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}\n\tgitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{\n\t\tTransport: git.HTTPS,\n\t\tUsername:  user,\n\t\tPassword:  bitbucketToken,\n\t\tCAFile:    caBundle,\n\t}, clientOpts...)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create a Git client: %w\", err)\n\t}\n\n\t// Install manifest config\n\tinstallOptions := install.Options{\n\t\tBaseURL:                rootArgs.defaults.BaseURL,\n\t\tVersion:                bootstrapArgs.version,\n\t\tNamespace:              *kubeconfigArgs.Namespace,\n\t\tComponents:             bootstrapComponents(),\n\t\tRegistry:               bootstrapArgs.registry,\n\t\tRegistryCredential:     bootstrapArgs.registryCredential,\n\t\tImagePullSecret:        bootstrapArgs.imagePullSecret,\n\t\tWatchAllNamespaces:     bootstrapArgs.watchAllNamespaces,\n\t\tNetworkPolicy:          bootstrapArgs.networkPolicy,\n\t\tLogLevel:               bootstrapArgs.logLevel.String(),\n\t\tNotificationController: rootArgs.defaults.NotificationController,\n\t\tManifestFile:           rootArgs.defaults.ManifestFile,\n\t\tTimeout:                rootArgs.timeout,\n\t\tTargetPath:             bServerArgs.path.ToSlash(),\n\t\tClusterDomain:          bootstrapArgs.clusterDomain,\n\t\tTolerationKeys:         bootstrapArgs.tolerationKeys,\n\t}\n\tif customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != \"\" {\n\t\tinstallOptions.BaseURL = customBaseURL\n\t}\n\n\t// Source generation and secret config\n\tsecretOpts := sourcesecret.Options{\n\t\tName:         bootstrapArgs.secretName,\n\t\tNamespace:    *kubeconfigArgs.Namespace,\n\t\tTargetPath:   bServerArgs.path.String(),\n\t\tManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,\n\t}\n\tif bootstrapArgs.tokenAuth {\n\t\tif bServerArgs.personal {\n\t\t\tsecretOpts.Username = bServerArgs.owner\n\t\t} else {\n\t\t\tsecretOpts.Username = bServerArgs.username\n\t\t}\n\t\tsecretOpts.Password = bitbucketToken\n\t\tsecretOpts.CACrt = caBundle\n\t} else {\n\t\tkeypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsecretOpts.Keypair = keypair\n\t\tsecretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)\n\t\tsecretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)\n\t\tsecretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve\n\n\t\tsecretOpts.SSHHostname = bServerArgs.hostname\n\t\tif bootstrapArgs.sshHostname != \"\" {\n\t\t\tsecretOpts.SSHHostname = bootstrapArgs.sshHostname\n\t\t}\n\t}\n\n\t// Sync manifest config\n\tsyncOpts := sync.Options{\n\t\tInterval:          bServerArgs.interval,\n\t\tName:              *kubeconfigArgs.Namespace,\n\t\tNamespace:         *kubeconfigArgs.Namespace,\n\t\tBranch:            bootstrapArgs.branch,\n\t\tSecret:            bootstrapArgs.secretName,\n\t\tTargetPath:        bServerArgs.path.ToSlash(),\n\t\tManifestFile:      sync.MakeDefaultOptions().ManifestFile,\n\t\tRecurseSubmodules: bootstrapArgs.recurseSubmodules,\n\t}\n\n\tentityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Bootstrap config\n\n\tbootstrapOpts := []bootstrap.GitProviderOption{\n\t\tbootstrap.WithProviderRepository(bServerArgs.owner, bServerArgs.repository, bServerArgs.personal),\n\t\tbootstrap.WithBranch(bootstrapArgs.branch),\n\t\tbootstrap.WithBootstrapTransportType(\"https\"),\n\t\tbootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),\n\t\tbootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),\n\t\tbootstrap.WithProviderTeamPermissions(mapTeamSlice(bServerArgs.teams, bServerDefaultPermission)),\n\t\tbootstrap.WithReadWriteKeyPermissions(bServerArgs.readWriteKey),\n\t\tbootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),\n\t\tbootstrap.WithLogger(logger),\n\t\tbootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),\n\t}\n\tif bootstrapArgs.sshHostname != \"\" {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))\n\t}\n\tif bootstrapArgs.tokenAuth {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType(\"https\"))\n\t}\n\tif !bServerArgs.private {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig(\"\", \"\", \"public\"))\n\t}\n\tif bServerArgs.reconcile {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())\n\t}\n\n\t// Setup bootstrapper with constructed configs\n\tb, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Run\n\treturn bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)\n}\n"
  },
  {
    "path": "cmd/flux/bootstrap_git.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/manifoldco/promptui\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\n\t\"github.com/fluxcd/pkg/git\"\n\t\"github.com/fluxcd/pkg/git/gogit\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/bootstrap\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync\"\n)\n\nvar bootstrapGitCmd = &cobra.Command{\n\tUse:   \"git\",\n\tShort: \"Deploy Flux on a cluster connected to a Git repository\",\n\tLong: `The bootstrap git command commits the Flux manifests to the\nbranch of a Git repository. And then it configures the target cluster to synchronize with\nthat repository. If the Flux components are present on the cluster, the bootstrap\ncommand will perform an upgrade if needed.`,\n\tExample: `  # Run bootstrap for a Git repository and authenticate with your SSH agent\n  flux bootstrap git --url=ssh://git@example.com/repository.git --path=clusters/my-cluster\n\n  # Run bootstrap for a Git repository and authenticate using a password\n  flux bootstrap git --url=https://example.com/repository.git --password=<password> --path=clusters/my-cluster\n\n  # Run bootstrap for a Git repository and authenticate using a password from environment variable\n  GIT_PASSWORD=<password> && flux bootstrap git --url=https://example.com/repository.git --path=clusters/my-cluster\n\n  # Run bootstrap for a Git repository with a passwordless private key\n  flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key> --path=clusters/my-cluster\n\n  # Run bootstrap for a Git repository with a private key and password\n  flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key> --password=<password> --path=clusters/my-cluster\n\n  # Run bootstrap for a Git repository on AWS CodeCommit\n  flux bootstrap git --url=ssh://<SSH-Key-ID>@git-codecommit.<region>.amazonaws.com/v1/repos/<repository> --private-key-file=<path/to/private.key> --password=<SSH-passphrase> --path=clusters/my-cluster\n\n  # Run bootstrap for a Git repository on Azure Devops\n  flux bootstrap git --url=ssh://git@ssh.dev.azure.com/v3/<org>/<project>/<repository> --private-key-file=<path/to/rsa-sha2-private.key> --ssh-hostkey-algos=rsa-sha2-512,rsa-sha2-256 --path=clusters/my-cluster\n\n  # Run bootstrap for a Git repository on Oracle VBS\n  flux bootstrap git --url=https://repository_url.git --with-bearer-token=true --password=<PAT> --path=clusters/my-cluster\n`,\n\tRunE: bootstrapGitCmdRun,\n}\n\ntype gitFlags struct {\n\turl                 string\n\tinterval            time.Duration\n\tpath                flags.SafeRelativePath\n\tusername            string\n\tpassword            string\n\tsilent              bool\n\tinsecureHttpAllowed bool\n\twithBearerToken     bool\n}\n\nconst (\n\tgitPasswordEnvVar = \"GIT_PASSWORD\"\n)\n\nvar gitArgs gitFlags\n\nfunc init() {\n\tbootstrapGitCmd.Flags().StringVar(&gitArgs.url, \"url\", \"\", \"Git repository URL\")\n\tbootstrapGitCmd.Flags().DurationVar(&gitArgs.interval, \"interval\", time.Minute, \"sync interval\")\n\tbootstrapGitCmd.Flags().Var(&gitArgs.path, \"path\", \"path relative to the repository root, when specified the cluster sync will be scoped to this path\")\n\tbootstrapGitCmd.Flags().StringVarP(&gitArgs.username, \"username\", \"u\", \"git\", \"basic authentication username\")\n\tbootstrapGitCmd.Flags().StringVarP(&gitArgs.password, \"password\", \"p\", \"\", \"basic authentication password\")\n\tbootstrapGitCmd.Flags().BoolVarP(&gitArgs.silent, \"silent\", \"s\", false, \"assumes the deploy key is already setup, skips confirmation\")\n\tbootstrapGitCmd.Flags().BoolVar(&gitArgs.insecureHttpAllowed, \"allow-insecure-http\", false, \"allows insecure HTTP connections\")\n\tbootstrapGitCmd.Flags().BoolVar(&gitArgs.withBearerToken, \"with-bearer-token\", false, \"use password as bearer token for Authorization header\")\n\n\tbootstrapCmd.AddCommand(bootstrapGitCmd)\n}\n\nfunc bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {\n\tif gitArgs.withBearerToken {\n\t\tbootstrapArgs.tokenAuth = true\n\t}\n\n\tgitPassword := os.Getenv(gitPasswordEnvVar)\n\tif gitPassword != \"\" && gitArgs.password == \"\" {\n\t\tgitArgs.password = gitPassword\n\t}\n\tif bootstrapArgs.tokenAuth && gitArgs.password == \"\" {\n\t\tvar err error\n\t\tgitPassword, err = readPasswordFromStdin(\"Please enter your Git repository password: \")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not read token: %w\", err)\n\t\t}\n\t\tgitArgs.password = gitPassword\n\t}\n\n\tif err := bootstrapValidate(); err != nil {\n\t\treturn err\n\t}\n\n\trepositoryURL, err := url.Parse(gitArgs.url)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif strings.Contains(repositoryURL.Hostname(), \"git-codecommit\") && strings.Contains(repositoryURL.Hostname(), \"amazonaws.com\") {\n\t\tif repositoryURL.Scheme == string(git.SSH) {\n\t\t\tif repositoryURL.User == nil {\n\t\t\t\treturn fmt.Errorf(\"invalid AWS CodeCommit url: ssh username should be specified in the url\")\n\t\t\t}\n\t\t\tif repositoryURL.User.Username() == git.DefaultPublicKeyAuthUser {\n\t\t\t\treturn fmt.Errorf(\"invalid AWS CodeCommit url: ssh username should be the SSH key ID for the provided private key\")\n\t\t\t}\n\t\t\tif bootstrapArgs.privateKeyFile == \"\" {\n\t\t\t\treturn fmt.Errorf(\"private key file is required for bootstrapping against AWS CodeCommit using ssh\")\n\t\t\t}\n\t\t}\n\t\tif repositoryURL.Scheme == string(git.HTTPS) && !bootstrapArgs.tokenAuth {\n\t\t\treturn fmt.Errorf(\"--token-auth=true must be specified for using an HTTPS AWS CodeCommit url\")\n\t\t}\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !bootstrapArgs.force {\n\t\terr = confirmBootstrap(ctx, kubeClient)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Manifest base\n\tif ver, err := getVersion(bootstrapArgs.version); err != nil {\n\t\treturn err\n\t} else {\n\t\tbootstrapArgs.version = ver\n\t}\n\tmanifestsBase, err := buildEmbeddedManifestBase()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(manifestsBase)\n\n\t// Lazy go-git repository\n\ttmpDir, err := manifestgen.MkdirTempAbs(\"\", \"flux-bootstrap-\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create temporary working dir: %w\", err)\n\t}\n\tdefer os.RemoveAll(tmpDir)\n\n\tvar caBundle []byte\n\tif bootstrapArgs.caFile != \"\" {\n\t\tvar err error\n\t\tcaBundle, err = os.ReadFile(bootstrapArgs.caFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to read TLS CA file: %w\", err)\n\t\t}\n\t}\n\tauthOpts, err := getAuthOpts(repositoryURL, caBundle)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create authentication options for %s: %w\", repositoryURL.String(), err)\n\t}\n\n\tclientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}\n\tif gitArgs.insecureHttpAllowed {\n\t\tclientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())\n\t}\n\tgitClient, err := gogit.NewClient(tmpDir, authOpts, clientOpts...)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create a Git client: %w\", err)\n\t}\n\n\t// Install manifest config\n\tinstallOptions := install.Options{\n\t\tBaseURL:                rootArgs.defaults.BaseURL,\n\t\tVersion:                bootstrapArgs.version,\n\t\tNamespace:              *kubeconfigArgs.Namespace,\n\t\tComponents:             bootstrapComponents(),\n\t\tRegistry:               bootstrapArgs.registry,\n\t\tRegistryCredential:     bootstrapArgs.registryCredential,\n\t\tImagePullSecret:        bootstrapArgs.imagePullSecret,\n\t\tWatchAllNamespaces:     bootstrapArgs.watchAllNamespaces,\n\t\tNetworkPolicy:          bootstrapArgs.networkPolicy,\n\t\tLogLevel:               bootstrapArgs.logLevel.String(),\n\t\tNotificationController: rootArgs.defaults.NotificationController,\n\t\tManifestFile:           rootArgs.defaults.ManifestFile,\n\t\tTimeout:                rootArgs.timeout,\n\t\tTargetPath:             gitArgs.path.ToSlash(),\n\t\tClusterDomain:          bootstrapArgs.clusterDomain,\n\t\tTolerationKeys:         bootstrapArgs.tolerationKeys,\n\t}\n\tif customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != \"\" {\n\t\tinstallOptions.BaseURL = customBaseURL\n\t}\n\n\t// Source generation and secret config\n\tsecretOpts := sourcesecret.Options{\n\t\tName:         bootstrapArgs.secretName,\n\t\tNamespace:    *kubeconfigArgs.Namespace,\n\t\tTargetPath:   gitArgs.path.String(),\n\t\tManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,\n\t}\n\n\tif bootstrapArgs.tokenAuth {\n\t\tif gitArgs.withBearerToken {\n\t\t\tsecretOpts.BearerToken = gitArgs.password\n\t\t} else {\n\t\t\tsecretOpts.Username = gitArgs.username\n\t\t\tsecretOpts.Password = gitArgs.password\n\t\t}\n\n\t\tsecretOpts.CACrt = caBundle\n\n\t\t// Remove port of the given host when not syncing over HTTP/S to not assume port for protocol\n\t\t// This _might_ be overwritten later on by e.g. --ssh-hostname\n\t\tif repositoryURL.Scheme != \"https\" && repositoryURL.Scheme != \"http\" {\n\t\t\trepositoryURL.Host = repositoryURL.Hostname()\n\t\t}\n\n\t\t// Configure repository URL to match auth config for sync.\n\t\trepositoryURL.User = nil\n\t\tif !gitArgs.insecureHttpAllowed {\n\t\t\trepositoryURL.Scheme = \"https\"\n\t\t}\n\t} else {\n\t\tsecretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)\n\t\tsecretOpts.Password = gitArgs.password\n\t\tsecretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)\n\t\tsecretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve\n\n\t\t// Configure repository URL to match auth config for sync\n\n\t\t// Override existing user when user is not already set\n\t\t// or when a username was passed in\n\t\tif repositoryURL.User == nil || gitArgs.username != \"git\" {\n\t\t\trepositoryURL.User = url.User(gitArgs.username)\n\t\t}\n\n\t\trepositoryURL.Scheme = \"ssh\"\n\t\tif bootstrapArgs.sshHostname != \"\" {\n\t\t\trepositoryURL.Host = bootstrapArgs.sshHostname\n\t\t}\n\n\t\tkeypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsecretOpts.Keypair = keypair\n\n\t\t// Configure last as it depends on the config above.\n\t\tsecretOpts.SSHHostname = repositoryURL.Host\n\t}\n\n\t// Sync manifest config\n\tsyncOpts := sync.Options{\n\t\tInterval:          gitArgs.interval,\n\t\tName:              *kubeconfigArgs.Namespace,\n\t\tNamespace:         *kubeconfigArgs.Namespace,\n\t\tURL:               repositoryURL.String(),\n\t\tBranch:            bootstrapArgs.branch,\n\t\tSecret:            bootstrapArgs.secretName,\n\t\tTargetPath:        gitArgs.path.ToSlash(),\n\t\tManifestFile:      sync.MakeDefaultOptions().ManifestFile,\n\t\tRecurseSubmodules: bootstrapArgs.recurseSubmodules,\n\t}\n\n\tentityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Bootstrap config\n\tbootstrapOpts := []bootstrap.GitOption{\n\t\tbootstrap.WithRepositoryURL(gitArgs.url),\n\t\tbootstrap.WithBranch(bootstrapArgs.branch),\n\t\tbootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),\n\t\tbootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),\n\t\tbootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),\n\t\tbootstrap.WithPostGenerateSecretFunc(promptPublicKey),\n\t\tbootstrap.WithLogger(logger),\n\t\tbootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),\n\t}\n\n\t// Setup bootstrapper with constructed configs\n\tb, err := bootstrap.NewPlainGitProvider(gitClient, kubeClient, bootstrapOpts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Run\n\treturn bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)\n}\n\n// getAuthOpts retruns a AuthOptions based on the scheme\n// of the given URL and the configured flags. If the protocol equals\n// \"ssh\" but no private key is configured, authentication using the local\n// SSH-agent is attempted.\nfunc getAuthOpts(u *url.URL, caBundle []byte) (*git.AuthOptions, error) {\n\tswitch u.Scheme {\n\tcase \"http\":\n\t\tif !gitArgs.insecureHttpAllowed {\n\t\t\treturn nil, fmt.Errorf(\"scheme http is insecure, pass --allow-insecure-http=true to allow it\")\n\t\t}\n\t\thttpAuth := git.AuthOptions{\n\t\t\tTransport: git.HTTP,\n\t\t}\n\t\tif gitArgs.withBearerToken {\n\t\t\thttpAuth.BearerToken = gitArgs.password\n\t\t} else {\n\t\t\thttpAuth.Username = gitArgs.username\n\t\t\thttpAuth.Password = gitArgs.password\n\t\t}\n\t\treturn &httpAuth, nil\n\tcase \"https\":\n\t\thttpsAuth := git.AuthOptions{\n\t\t\tTransport: git.HTTPS,\n\t\t\tCAFile:    caBundle,\n\t\t}\n\t\tif gitArgs.withBearerToken {\n\t\t\thttpsAuth.BearerToken = gitArgs.password\n\t\t} else {\n\t\t\thttpsAuth.Username = gitArgs.username\n\t\t\thttpsAuth.Password = gitArgs.password\n\t\t}\n\t\treturn &httpsAuth, nil\n\tcase \"ssh\":\n\t\tauthOpts := &git.AuthOptions{\n\t\t\tTransport: git.SSH,\n\t\t\tUsername:  u.User.Username(),\n\t\t\tPassword:  gitArgs.password,\n\t\t}\n\t\tif bootstrapArgs.privateKeyFile != \"\" {\n\t\t\tpk, err := os.ReadFile(bootstrapArgs.privateKeyFile)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tkh, err := sourcesecret.ScanHostKey(u.Host)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tauthOpts.Identity = pk\n\t\t\tauthOpts.KnownHosts = kh\n\t\t}\n\t\treturn authOpts, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"scheme %q is not supported\", u.Scheme)\n\t}\n}\n\nfunc promptPublicKey(ctx context.Context, secret corev1.Secret, _ sourcesecret.Options) error {\n\tppk, ok := secret.StringData[sourcesecret.PublicKeySecretKey]\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tlogger.Successf(\"public key: %s\", strings.TrimSpace(ppk))\n\n\tif !gitArgs.silent {\n\t\tprompt := promptui.Prompt{\n\t\t\tLabel:     \"Please give the key access to your repository\",\n\t\t\tIsConfirm: true,\n\t\t}\n\t\t_, err := prompt.Run()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"aborting\")\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/bootstrap_gitea.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/fluxcd/pkg/git\"\n\t\"github.com/fluxcd/pkg/git/gogit\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/bootstrap\"\n\t\"github.com/fluxcd/flux2/v2/pkg/bootstrap/provider\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync\"\n)\n\nvar bootstrapGiteaCmd = &cobra.Command{\n\tUse:   \"gitea\",\n\tShort: \"Deploy Flux on a cluster connected to a Gitea repository\",\n\tLong: `The bootstrap gitea command creates the Gitea repository if it doesn't exists and\ncommits the Flux manifests to the specified branch.\nThen it configures the target cluster to synchronize with that repository.\nIf the Flux components are present on the cluster,\nthe bootstrap command will perform an upgrade if needed.`,\n\tExample: `  # Create a Gitea personal access token and export it as an env var\n  export GITEA_TOKEN=<my-token>\n\n  # Run bootstrap for a private repository owned by a Gitea organization\n  flux bootstrap gitea --owner=<organization> --repository=<repository name> --path=clusters/my-cluster\n\n  # Run bootstrap for a private repository and assign organization teams to it\n  flux bootstrap gitea --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug> --path=clusters/my-cluster\n\n  # Run bootstrap for a private repository and assign organization teams with their access level(e.g maintain, admin) to it\n  flux bootstrap gitea --owner=<organization> --repository=<repository name> --team=<team1 slug>:<access-level> --path=clusters/my-cluster\n\n  # Run bootstrap for a public repository on a personal account\n  flux bootstrap gitea --owner=<user> --repository=<repository name> --private=false --personal=true --path=clusters/my-cluster\n\n  # Run bootstrap for a private repository hosted on Gitea Enterprise using SSH auth\n  flux bootstrap gitea --owner=<organization> --repository=<repository name> --hostname=<domain> --ssh-hostname=<domain> --path=clusters/my-cluster\n\n  # Run bootstrap for a private repository hosted on Gitea Enterprise using HTTPS auth\n  flux bootstrap gitea --owner=<organization> --repository=<repository name> --hostname=<domain> --token-auth --path=clusters/my-cluster\n\n  # Run bootstrap for an existing repository with a branch named main\n  flux bootstrap gitea --owner=<organization> --repository=<repository name> --branch=main --path=clusters/my-cluster`,\n\tRunE: bootstrapGiteaCmdRun,\n}\n\ntype giteaFlags struct {\n\towner        string\n\trepository   string\n\tinterval     time.Duration\n\tpersonal     bool\n\tprivate      bool\n\thostname     string\n\tpath         flags.SafeRelativePath\n\tteams        []string\n\treadWriteKey bool\n\treconcile    bool\n}\n\nconst (\n\tgtDefaultPermission = \"maintain\"\n\tgtDefaultDomain     = \"gitea.com\"\n\tgtTokenEnvVar       = \"GITEA_TOKEN\"\n)\n\nvar giteaArgs giteaFlags\n\nfunc init() {\n\tbootstrapGiteaCmd.Flags().StringVar(&giteaArgs.owner, \"owner\", \"\", \"Gitea user or organization name\")\n\tbootstrapGiteaCmd.Flags().StringVar(&giteaArgs.repository, \"repository\", \"\", \"Gitea repository name\")\n\tbootstrapGiteaCmd.Flags().StringSliceVar(&giteaArgs.teams, \"team\", []string{}, \"Gitea team and the access to be given to it(team:maintain). Defaults to maintainer access if no access level is specified (also accepts comma-separated values)\")\n\tbootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.personal, \"personal\", false, \"if true, the owner is assumed to be a Gitea user; otherwise an org\")\n\tbootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.private, \"private\", true, \"if true, the repository is setup or configured as private\")\n\tbootstrapGiteaCmd.Flags().DurationVar(&giteaArgs.interval, \"interval\", time.Minute, \"sync interval\")\n\tbootstrapGiteaCmd.Flags().StringVar(&giteaArgs.hostname, \"hostname\", gtDefaultDomain, \"Gitea hostname\")\n\tbootstrapGiteaCmd.Flags().Var(&giteaArgs.path, \"path\", \"path relative to the repository root, when specified the cluster sync will be scoped to this path\")\n\tbootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.readWriteKey, \"read-write-key\", false, \"if true, the deploy key is configured with read/write permissions\")\n\tbootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.reconcile, \"reconcile\", false, \"if true, the configured options are also reconciled if the repository already exists\")\n\n\tbootstrapCmd.AddCommand(bootstrapGiteaCmd)\n}\n\nfunc bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {\n\tgtToken := os.Getenv(gtTokenEnvVar)\n\tif gtToken == \"\" {\n\t\tvar err error\n\t\tgtToken, err = readPasswordFromStdin(\"Please enter your Gitea personal access token (PAT): \")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not read token: %w\", err)\n\t\t}\n\t}\n\n\tif err := bootstrapValidate(); err != nil {\n\t\treturn err\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Manifest base\n\tif ver, err := getVersion(bootstrapArgs.version); err != nil {\n\t\treturn err\n\t} else {\n\t\tbootstrapArgs.version = ver\n\t}\n\tmanifestsBase, err := buildEmbeddedManifestBase()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(manifestsBase)\n\n\tvar caBundle []byte\n\tif bootstrapArgs.caFile != \"\" {\n\t\tvar err error\n\t\tcaBundle, err = os.ReadFile(bootstrapArgs.caFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to read TLS CA file: %w\", err)\n\t\t}\n\t}\n\t// Build Gitea provider\n\tproviderCfg := provider.Config{\n\t\tProvider: provider.GitProviderGitea,\n\t\tHostname: giteaArgs.hostname,\n\t\tToken:    gtToken,\n\t\tCaBundle: caBundle,\n\t}\n\tproviderClient, err := provider.BuildGitProvider(providerCfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttmpDir, err := manifestgen.MkdirTempAbs(\"\", \"flux-bootstrap-\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create temporary working dir: %w\", err)\n\t}\n\tdefer os.RemoveAll(tmpDir)\n\n\tclientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}\n\tgitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{\n\t\tTransport: git.HTTPS,\n\t\tUsername:  giteaArgs.owner,\n\t\tPassword:  gtToken,\n\t\tCAFile:    caBundle,\n\t}, clientOpts...)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create a Git client: %w\", err)\n\t}\n\n\t// Install manifest config\n\tinstallOptions := install.Options{\n\t\tBaseURL:                rootArgs.defaults.BaseURL,\n\t\tVersion:                bootstrapArgs.version,\n\t\tNamespace:              *kubeconfigArgs.Namespace,\n\t\tComponents:             bootstrapComponents(),\n\t\tRegistry:               bootstrapArgs.registry,\n\t\tRegistryCredential:     bootstrapArgs.registryCredential,\n\t\tImagePullSecret:        bootstrapArgs.imagePullSecret,\n\t\tWatchAllNamespaces:     bootstrapArgs.watchAllNamespaces,\n\t\tNetworkPolicy:          bootstrapArgs.networkPolicy,\n\t\tLogLevel:               bootstrapArgs.logLevel.String(),\n\t\tNotificationController: rootArgs.defaults.NotificationController,\n\t\tManifestFile:           rootArgs.defaults.ManifestFile,\n\t\tTimeout:                rootArgs.timeout,\n\t\tTargetPath:             giteaArgs.path.ToSlash(),\n\t\tClusterDomain:          bootstrapArgs.clusterDomain,\n\t\tTolerationKeys:         bootstrapArgs.tolerationKeys,\n\t}\n\tif customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != \"\" {\n\t\tinstallOptions.BaseURL = customBaseURL\n\t}\n\n\t// Source generation and secret config\n\tsecretOpts := sourcesecret.Options{\n\t\tName:         bootstrapArgs.secretName,\n\t\tNamespace:    *kubeconfigArgs.Namespace,\n\t\tTargetPath:   giteaArgs.path.ToSlash(),\n\t\tManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,\n\t}\n\tif bootstrapArgs.tokenAuth {\n\t\tsecretOpts.Username = \"git\"\n\t\tsecretOpts.Password = gtToken\n\t\tsecretOpts.CACrt = caBundle\n\t} else {\n\t\tsecretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)\n\t\tsecretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)\n\t\tsecretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve\n\n\t\tsecretOpts.SSHHostname = giteaArgs.hostname\n\t\tif bootstrapArgs.sshHostname != \"\" {\n\t\t\tsecretOpts.SSHHostname = bootstrapArgs.sshHostname\n\t\t}\n\t}\n\n\t// Sync manifest config\n\tsyncOpts := sync.Options{\n\t\tInterval:          giteaArgs.interval,\n\t\tName:              *kubeconfigArgs.Namespace,\n\t\tNamespace:         *kubeconfigArgs.Namespace,\n\t\tBranch:            bootstrapArgs.branch,\n\t\tSecret:            bootstrapArgs.secretName,\n\t\tTargetPath:        giteaArgs.path.ToSlash(),\n\t\tManifestFile:      sync.MakeDefaultOptions().ManifestFile,\n\t\tRecurseSubmodules: bootstrapArgs.recurseSubmodules,\n\t}\n\n\tentityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Bootstrap config\n\tbootstrapOpts := []bootstrap.GitProviderOption{\n\t\tbootstrap.WithProviderRepository(giteaArgs.owner, giteaArgs.repository, giteaArgs.personal),\n\t\tbootstrap.WithBranch(bootstrapArgs.branch),\n\t\tbootstrap.WithBootstrapTransportType(\"https\"),\n\t\tbootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),\n\t\tbootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),\n\t\tbootstrap.WithProviderTeamPermissions(mapTeamSlice(giteaArgs.teams, gtDefaultPermission)),\n\t\tbootstrap.WithReadWriteKeyPermissions(giteaArgs.readWriteKey),\n\t\tbootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),\n\t\tbootstrap.WithLogger(logger),\n\t\tbootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),\n\t}\n\tif bootstrapArgs.sshHostname != \"\" {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))\n\t}\n\tif bootstrapArgs.tokenAuth {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType(\"https\"))\n\t}\n\tif !giteaArgs.private {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig(\"\", \"\", \"public\"))\n\t}\n\tif giteaArgs.reconcile {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())\n\t}\n\n\t// Setup bootstrapper with constructed configs\n\tb, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Run\n\treturn bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)\n}\n"
  },
  {
    "path": "cmd/flux/bootstrap_github.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/fluxcd/pkg/git\"\n\t\"github.com/fluxcd/pkg/git/gogit\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/bootstrap\"\n\t\"github.com/fluxcd/flux2/v2/pkg/bootstrap/provider\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync\"\n)\n\nvar bootstrapGitHubCmd = &cobra.Command{\n\tUse:   \"github\",\n\tShort: \"Deploy Flux on a cluster connected to a GitHub repository\",\n\tLong: `The bootstrap github command creates the GitHub repository if it doesn't exists and\ncommits the Flux manifests to the specified branch.\nThen it configures the target cluster to synchronize with that repository.\nIf the Flux components are present on the cluster,\nthe bootstrap command will perform an upgrade if needed.`,\n\tExample: `  # Create a GitHub personal access token and export it as an env var\n  export GITHUB_TOKEN=<my-token>\n\n  # Run bootstrap for a private repository owned by a GitHub organization\n  flux bootstrap github --owner=<organization> --repository=<repository name> --path=clusters/my-cluster\n\n  # Run bootstrap for a private repository and assign organization teams to it\n  flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug> --path=clusters/my-cluster\n\n  # Run bootstrap for a private repository and assign organization teams with their access level(e.g maintain, admin) to it\n  flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug>:<access-level> --path=clusters/my-cluster\n\n  # Run bootstrap for a public repository on a personal account\n  flux bootstrap github --owner=<user> --repository=<repository name> --private=false --personal=true --path=clusters/my-cluster\n\n  # Run bootstrap for a private repository hosted on GitHub Enterprise using SSH auth\n  flux bootstrap github --owner=<organization> --repository=<repository name> --hostname=<domain> --ssh-hostname=<domain> --path=clusters/my-cluster\n\n  # Run bootstrap for a private repository hosted on GitHub Enterprise using HTTPS auth\n  flux bootstrap github --owner=<organization> --repository=<repository name> --hostname=<domain> --token-auth --path=clusters/my-cluster\n\n  # Run bootstrap for an existing repository with a branch named main\n  flux bootstrap github --owner=<organization> --repository=<repository name> --branch=main --path=clusters/my-cluster`,\n\tRunE: bootstrapGitHubCmdRun,\n}\n\ntype githubFlags struct {\n\towner        string\n\trepository   string\n\tinterval     time.Duration\n\tpersonal     bool\n\tprivate      bool\n\thostname     string\n\tpath         flags.SafeRelativePath\n\tteams        []string\n\treadWriteKey bool\n\treconcile    bool\n}\n\nconst (\n\tghDefaultPermission = \"maintain\"\n\tghDefaultDomain     = \"github.com\"\n\tghTokenEnvVar       = \"GITHUB_TOKEN\"\n)\n\nvar githubArgs githubFlags\n\nfunc init() {\n\tbootstrapGitHubCmd.Flags().StringVar(&githubArgs.owner, \"owner\", \"\", \"GitHub user or organization name\")\n\tbootstrapGitHubCmd.Flags().StringVar(&githubArgs.repository, \"repository\", \"\", \"GitHub repository name\")\n\tbootstrapGitHubCmd.Flags().StringSliceVar(&githubArgs.teams, \"team\", []string{}, \"GitHub team and the access to be given to it(team:maintain). Defaults to maintainer access if no access level is specified (also accepts comma-separated values)\")\n\tbootstrapGitHubCmd.Flags().BoolVar(&githubArgs.personal, \"personal\", false, \"if true, the owner is assumed to be a GitHub user; otherwise an org\")\n\tbootstrapGitHubCmd.Flags().BoolVar(&githubArgs.private, \"private\", true, \"if true, the repository is setup or configured as private\")\n\tbootstrapGitHubCmd.Flags().DurationVar(&githubArgs.interval, \"interval\", time.Minute, \"sync interval\")\n\tbootstrapGitHubCmd.Flags().StringVar(&githubArgs.hostname, \"hostname\", ghDefaultDomain, \"GitHub hostname\")\n\tbootstrapGitHubCmd.Flags().Var(&githubArgs.path, \"path\", \"path relative to the repository root, when specified the cluster sync will be scoped to this path\")\n\tbootstrapGitHubCmd.Flags().BoolVar(&githubArgs.readWriteKey, \"read-write-key\", false, \"if true, the deploy key is configured with read/write permissions\")\n\tbootstrapGitHubCmd.Flags().BoolVar(&githubArgs.reconcile, \"reconcile\", false, \"if true, the configured options are also reconciled if the repository already exists\")\n\n\tbootstrapCmd.AddCommand(bootstrapGitHubCmd)\n}\n\nfunc bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {\n\tghToken := os.Getenv(ghTokenEnvVar)\n\tif ghToken == \"\" {\n\t\tvar err error\n\t\tghToken, err = readPasswordFromStdin(\"Please enter your GitHub personal access token (PAT): \")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not read token: %w\", err)\n\t\t}\n\t}\n\n\tif err := bootstrapValidate(); err != nil {\n\t\treturn err\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !bootstrapArgs.force {\n\t\terr = confirmBootstrap(ctx, kubeClient)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Manifest base\n\tif ver, err := getVersion(bootstrapArgs.version); err != nil {\n\t\treturn err\n\t} else {\n\t\tbootstrapArgs.version = ver\n\t}\n\tmanifestsBase, err := buildEmbeddedManifestBase()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(manifestsBase)\n\n\tvar caBundle []byte\n\tif bootstrapArgs.caFile != \"\" {\n\t\tvar err error\n\t\tcaBundle, err = os.ReadFile(bootstrapArgs.caFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to read TLS CA file: %w\", err)\n\t\t}\n\t}\n\t// Build GitHub provider\n\tproviderCfg := provider.Config{\n\t\tProvider: provider.GitProviderGitHub,\n\t\tHostname: githubArgs.hostname,\n\t\tToken:    ghToken,\n\t\tCaBundle: caBundle,\n\t}\n\tproviderClient, err := provider.BuildGitProvider(providerCfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttmpDir, err := manifestgen.MkdirTempAbs(\"\", \"flux-bootstrap-\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create temporary working dir: %w\", err)\n\t}\n\tdefer os.RemoveAll(tmpDir)\n\n\tclientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}\n\tgitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{\n\t\tTransport: git.HTTPS,\n\t\tUsername:  githubArgs.owner,\n\t\tPassword:  ghToken,\n\t\tCAFile:    caBundle,\n\t}, clientOpts...)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create a Git client: %w\", err)\n\t}\n\n\t// Install manifest config\n\tinstallOptions := install.Options{\n\t\tBaseURL:                rootArgs.defaults.BaseURL,\n\t\tVersion:                bootstrapArgs.version,\n\t\tNamespace:              *kubeconfigArgs.Namespace,\n\t\tComponents:             bootstrapComponents(),\n\t\tRegistry:               bootstrapArgs.registry,\n\t\tRegistryCredential:     bootstrapArgs.registryCredential,\n\t\tImagePullSecret:        bootstrapArgs.imagePullSecret,\n\t\tWatchAllNamespaces:     bootstrapArgs.watchAllNamespaces,\n\t\tNetworkPolicy:          bootstrapArgs.networkPolicy,\n\t\tLogLevel:               bootstrapArgs.logLevel.String(),\n\t\tNotificationController: rootArgs.defaults.NotificationController,\n\t\tManifestFile:           rootArgs.defaults.ManifestFile,\n\t\tTimeout:                rootArgs.timeout,\n\t\tTargetPath:             githubArgs.path.ToSlash(),\n\t\tClusterDomain:          bootstrapArgs.clusterDomain,\n\t\tTolerationKeys:         bootstrapArgs.tolerationKeys,\n\t}\n\tif customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != \"\" {\n\t\tinstallOptions.BaseURL = customBaseURL\n\t}\n\n\t// Source generation and secret config\n\tsecretOpts := sourcesecret.Options{\n\t\tName:         bootstrapArgs.secretName,\n\t\tNamespace:    *kubeconfigArgs.Namespace,\n\t\tTargetPath:   githubArgs.path.ToSlash(),\n\t\tManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,\n\t}\n\tif bootstrapArgs.tokenAuth {\n\t\tsecretOpts.Username = \"git\"\n\t\tsecretOpts.Password = ghToken\n\t\tsecretOpts.CACrt = caBundle\n\t} else {\n\t\tsecretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)\n\t\tsecretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)\n\t\tsecretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve\n\n\t\tsecretOpts.SSHHostname = githubArgs.hostname\n\t\tif bootstrapArgs.sshHostname != \"\" {\n\t\t\tsecretOpts.SSHHostname = bootstrapArgs.sshHostname\n\t\t}\n\t}\n\n\t// Sync manifest config\n\tsyncOpts := sync.Options{\n\t\tInterval:          githubArgs.interval,\n\t\tName:              *kubeconfigArgs.Namespace,\n\t\tNamespace:         *kubeconfigArgs.Namespace,\n\t\tBranch:            bootstrapArgs.branch,\n\t\tSecret:            bootstrapArgs.secretName,\n\t\tTargetPath:        githubArgs.path.ToSlash(),\n\t\tManifestFile:      sync.MakeDefaultOptions().ManifestFile,\n\t\tRecurseSubmodules: bootstrapArgs.recurseSubmodules,\n\t}\n\n\tentityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Bootstrap config\n\tbootstrapOpts := []bootstrap.GitProviderOption{\n\t\tbootstrap.WithProviderRepository(githubArgs.owner, githubArgs.repository, githubArgs.personal),\n\t\tbootstrap.WithBranch(bootstrapArgs.branch),\n\t\tbootstrap.WithBootstrapTransportType(\"https\"),\n\t\tbootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),\n\t\tbootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),\n\t\tbootstrap.WithProviderTeamPermissions(mapTeamSlice(githubArgs.teams, ghDefaultPermission)),\n\t\tbootstrap.WithReadWriteKeyPermissions(githubArgs.readWriteKey),\n\t\tbootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),\n\t\tbootstrap.WithLogger(logger),\n\t\tbootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),\n\t}\n\tif bootstrapArgs.sshHostname != \"\" {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))\n\t}\n\tif bootstrapArgs.tokenAuth {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType(\"https\"))\n\t}\n\tif !githubArgs.private {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig(\"\", \"\", \"public\"))\n\t}\n\tif githubArgs.reconcile {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())\n\t}\n\n\t// Setup bootstrapper with constructed configs\n\tb, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Run\n\treturn bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)\n}\n"
  },
  {
    "path": "cmd/flux/bootstrap_gitlab.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fluxcd/go-git-providers/gitprovider\"\n\t\"github.com/fluxcd/pkg/git\"\n\t\"github.com/fluxcd/pkg/git/gogit\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/bootstrap\"\n\t\"github.com/fluxcd/flux2/v2/pkg/bootstrap/provider\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync\"\n)\n\nvar bootstrapGitLabCmd = &cobra.Command{\n\tUse:   \"gitlab\",\n\tShort: \"Deploy Flux on a cluster connected to a GitLab repository\",\n\tLong: `The bootstrap gitlab command creates the GitLab repository if it doesn't exist and\ncommits the Flux manifests to the specified branch.\nThen it configures the target cluster to synchronize with that repository.\nIf the Flux components are present on the cluster,\nthe bootstrap command will perform an upgrade if needed.`,\n\tExample: `  # Create a GitLab API token and export it as an env var\n  export GITLAB_TOKEN=<my-token>\n\n  # Run bootstrap for a private repository using HTTPS token authentication\n  flux bootstrap gitlab --owner=<group> --repository=<repository name> --token-auth\n\n  # Run bootstrap for a private repository using SSH authentication\n  flux bootstrap gitlab --owner=<group> --repository=<repository name>\n\n  # Run bootstrap for a repository path\n  flux bootstrap gitlab --owner=<group> --repository=<repository name> --path=dev-cluster\n\n  # Run bootstrap for a public repository\n  flux bootstrap gitlab --owner=<group> --repository=<repository name> --visibility=public  --token-auth\n\n  # Run bootstrap for a private repository hosted on a GitLab server\n  flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<gitlab_url> --token-auth\n\n  # Run bootstrap for an existing repository with a branch named main\n  flux bootstrap gitlab --owner=<group> --repository=<repository name> --branch=main --token-auth\n\n  # Run bootstrap for a private repository using Deploy Token authentication\n  flux bootstrap gitlab --owner=<group> --repository=<repository name> --deploy-token-auth\n  `,\n\tRunE: bootstrapGitLabCmdRun,\n}\n\nconst (\n\tglDefaultPermission = \"maintain\"\n\tglDefaultDomain     = \"gitlab.com\"\n\tglTokenEnvVar       = \"GITLAB_TOKEN\"\n\tgitlabProjectRegex  = `\\A[[:alnum:]\\x{00A9}-\\x{1f9ff}_][[:alnum:]\\p{Pd}\\x{00A9}-\\x{1f9ff}_\\.]*\\z`\n)\n\ntype gitlabFlags struct {\n\towner           string\n\trepository      string\n\tinterval        time.Duration\n\tpersonal        bool\n\tvisibility      flags.GitLabVisibility\n\tprivate         bool\n\thostname        string\n\tpath            flags.SafeRelativePath\n\tteams           []string\n\treadWriteKey    bool\n\treconcile       bool\n\tdeployTokenAuth bool\n}\n\nfunc NewGitlabFlags() gitlabFlags {\n\treturn gitlabFlags{\n\t\tvisibility: flags.GitLabVisibility(gitprovider.RepositoryVisibilityPrivate),\n\t}\n}\n\nvar gitlabArgs = NewGitlabFlags()\n\nfunc init() {\n\tbootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.owner, \"owner\", \"\", \"GitLab user or group name\")\n\tbootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.repository, \"repository\", \"\", \"GitLab repository name\")\n\tbootstrapGitLabCmd.Flags().StringSliceVar(&gitlabArgs.teams, \"team\", []string{}, \"GitLab teams to be given maintainer access (also accepts comma-separated values)\")\n\tbootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.personal, \"personal\", false, \"if true, the owner is assumed to be a GitLab user; otherwise a group\")\n\tbootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.private, \"private\", true, \"if true, the repository is setup or configured as private\")\n\tbootstrapGitLabCmd.Flags().MarkDeprecated(\"private\", \"use --visibility instead\")\n\tbootstrapGitLabCmd.Flags().Var(&gitlabArgs.visibility, \"visibility\", gitlabArgs.visibility.Description())\n\tbootstrapGitLabCmd.Flags().DurationVar(&gitlabArgs.interval, \"interval\", time.Minute, \"sync interval\")\n\tbootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.hostname, \"hostname\", glDefaultDomain, \"GitLab hostname\")\n\tbootstrapGitLabCmd.Flags().Var(&gitlabArgs.path, \"path\", \"path relative to the repository root, when specified the cluster sync will be scoped to this path\")\n\tbootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.readWriteKey, \"read-write-key\", false, \"if true, the deploy key is configured with read/write permissions\")\n\tbootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.reconcile, \"reconcile\", false, \"if true, the configured options are also reconciled if the repository already exists\")\n\tbootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.deployTokenAuth, \"deploy-token-auth\", false, \"when enabled, a Project Deploy Token is generated and will be used instead of the SSH deploy token\")\n\n\tbootstrapCmd.AddCommand(bootstrapGitLabCmd)\n}\n\nfunc bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {\n\tglToken := os.Getenv(glTokenEnvVar)\n\tif glToken == \"\" {\n\t\tvar err error\n\t\tglToken, err = readPasswordFromStdin(\"Please enter your GitLab personal access token (PAT): \")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not read token: %w\", err)\n\t\t}\n\t}\n\n\tif projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, gitlabArgs.repository); err != nil || !projectNameIsValid {\n\t\tif err == nil {\n\t\t\terr = fmt.Errorf(\"%s is an invalid project name for gitlab.\\nIt can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.\", gitlabArgs.repository)\n\t\t}\n\t\treturn err\n\t}\n\n\tif bootstrapArgs.tokenAuth && gitlabArgs.deployTokenAuth {\n\t\treturn fmt.Errorf(\"--token-auth and --deploy-token-auth cannot be set both.\")\n\t}\n\n\tif !gitlabArgs.private {\n\t\tgitlabArgs.visibility.Set(string(gitprovider.RepositoryVisibilityPublic))\n\t\tcmd.Println(\"Using visibility public as --private=false\")\n\t}\n\n\tif err := bootstrapValidate(); err != nil {\n\t\treturn err\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !bootstrapArgs.force {\n\t\terr = confirmBootstrap(ctx, kubeClient)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Manifest base\n\tif ver, err := getVersion(bootstrapArgs.version); err != nil {\n\t\treturn err\n\t} else {\n\t\tbootstrapArgs.version = ver\n\t}\n\tmanifestsBase, err := buildEmbeddedManifestBase()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(manifestsBase)\n\n\tvar caBundle []byte\n\tif bootstrapArgs.caFile != \"\" {\n\t\tvar err error\n\t\tcaBundle, err = os.ReadFile(bootstrapArgs.caFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to read TLS CA file: %w\", err)\n\t\t}\n\t}\n\n\t// Build GitLab provider\n\tproviderCfg := provider.Config{\n\t\tProvider: provider.GitProviderGitLab,\n\t\tHostname: gitlabArgs.hostname,\n\t\tToken:    glToken,\n\t\tCaBundle: caBundle,\n\t}\n\t// Workaround for: https://github.com/fluxcd/go-git-providers/issues/55\n\tif hostname := providerCfg.Hostname; hostname != glDefaultDomain &&\n\t\t!strings.HasPrefix(hostname, \"https://\") &&\n\t\t!strings.HasPrefix(hostname, \"http://\") {\n\t\tproviderCfg.Hostname = \"https://\" + providerCfg.Hostname\n\t}\n\tproviderClient, err := provider.BuildGitProvider(providerCfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Lazy go-git repository\n\ttmpDir, err := manifestgen.MkdirTempAbs(\"\", \"flux-bootstrap-\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create temporary working dir: %w\", err)\n\t}\n\tdefer os.RemoveAll(tmpDir)\n\n\tclientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}\n\tgitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{\n\t\tTransport: git.HTTPS,\n\t\tUsername:  gitlabArgs.owner,\n\t\tPassword:  glToken,\n\t\tCAFile:    caBundle,\n\t}, clientOpts...)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create a Git client: %w\", err)\n\t}\n\n\t// Install manifest config\n\tinstallOptions := install.Options{\n\t\tBaseURL:                rootArgs.defaults.BaseURL,\n\t\tVersion:                bootstrapArgs.version,\n\t\tNamespace:              *kubeconfigArgs.Namespace,\n\t\tComponents:             bootstrapComponents(),\n\t\tRegistry:               bootstrapArgs.registry,\n\t\tRegistryCredential:     bootstrapArgs.registryCredential,\n\t\tImagePullSecret:        bootstrapArgs.imagePullSecret,\n\t\tWatchAllNamespaces:     bootstrapArgs.watchAllNamespaces,\n\t\tNetworkPolicy:          bootstrapArgs.networkPolicy,\n\t\tLogLevel:               bootstrapArgs.logLevel.String(),\n\t\tNotificationController: rootArgs.defaults.NotificationController,\n\t\tManifestFile:           rootArgs.defaults.ManifestFile,\n\t\tTimeout:                rootArgs.timeout,\n\t\tTargetPath:             gitlabArgs.path.ToSlash(),\n\t\tClusterDomain:          bootstrapArgs.clusterDomain,\n\t\tTolerationKeys:         bootstrapArgs.tolerationKeys,\n\t}\n\tif customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != \"\" {\n\t\tinstallOptions.BaseURL = customBaseURL\n\t}\n\n\t// Source generation and secret config\n\tsecretOpts := sourcesecret.Options{\n\t\tName:         bootstrapArgs.secretName,\n\t\tNamespace:    *kubeconfigArgs.Namespace,\n\t\tTargetPath:   gitlabArgs.path.String(),\n\t\tManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,\n\t}\n\tif bootstrapArgs.tokenAuth {\n\t\tsecretOpts.Username = \"git\"\n\t\tsecretOpts.Password = glToken\n\t\tsecretOpts.CACrt = caBundle\n\t} else if gitlabArgs.deployTokenAuth {\n\t\t// the actual deploy token will be reconciled later\n\t\tsecretOpts.CACrt = caBundle\n\t} else {\n\t\tkeypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsecretOpts.Keypair = keypair\n\t\tsecretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)\n\t\tsecretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)\n\t\tsecretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve\n\n\t\tsecretOpts.SSHHostname = gitlabArgs.hostname\n\t\tif bootstrapArgs.sshHostname != \"\" {\n\t\t\tsecretOpts.SSHHostname = bootstrapArgs.sshHostname\n\t\t}\n\t}\n\n\t// Sync manifest config\n\tsyncOpts := sync.Options{\n\t\tInterval:          gitlabArgs.interval,\n\t\tName:              *kubeconfigArgs.Namespace,\n\t\tNamespace:         *kubeconfigArgs.Namespace,\n\t\tBranch:            bootstrapArgs.branch,\n\t\tSecret:            bootstrapArgs.secretName,\n\t\tTargetPath:        gitlabArgs.path.ToSlash(),\n\t\tManifestFile:      sync.MakeDefaultOptions().ManifestFile,\n\t\tRecurseSubmodules: bootstrapArgs.recurseSubmodules,\n\t}\n\n\tentityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Bootstrap config\n\tbootstrapOpts := []bootstrap.GitProviderOption{\n\t\tbootstrap.WithProviderRepository(gitlabArgs.owner, gitlabArgs.repository, gitlabArgs.personal),\n\t\tbootstrap.WithProviderVisibility(gitlabArgs.visibility.String()),\n\t\tbootstrap.WithBranch(bootstrapArgs.branch),\n\t\tbootstrap.WithBootstrapTransportType(\"https\"),\n\t\tbootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),\n\t\tbootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),\n\t\tbootstrap.WithProviderTeamPermissions(mapTeamSlice(gitlabArgs.teams, glDefaultPermission)),\n\t\tbootstrap.WithReadWriteKeyPermissions(gitlabArgs.readWriteKey),\n\t\tbootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),\n\t\tbootstrap.WithLogger(logger),\n\t\tbootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),\n\t}\n\tif bootstrapArgs.sshHostname != \"\" {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))\n\t}\n\tif bootstrapArgs.tokenAuth || gitlabArgs.deployTokenAuth {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType(\"https\"))\n\t}\n\tif gitlabArgs.deployTokenAuth {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithDeployTokenAuth())\n\t}\n\tif gitlabArgs.reconcile {\n\t\tbootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())\n\t}\n\n\t// Setup bootstrapper with constructed configs\n\tb, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Run\n\treturn bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)\n}\n"
  },
  {
    "path": "cmd/flux/build.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar buildCmd = &cobra.Command{\n\tUse:   \"build\",\n\tShort: \"Build a flux resource\",\n\tLong:  `The build command is used to build flux resources.`,\n}\n\nfunc init() {\n\trootCmd.AddCommand(buildCmd)\n}\n"
  },
  {
    "path": "cmd/flux/build_artifact.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/fluxcd/pkg/oci\"\n\t\"github.com/fluxcd/pkg/sourceignore\"\n)\n\nvar buildArtifactCmd = &cobra.Command{\n\tUse:   \"artifact\",\n\tShort: \"Build artifact\",\n\tLong: `The build artifact command creates a tgz file with the manifests\nfrom the given directory or a single manifest file.`,\n\tExample: `  # Build the given manifests directory into an artifact\n  flux build artifact --path ./path/to/local/manifests --output ./path/to/artifact.tgz\n\n  # Build the given single manifest file into an artifact\n  flux build artifact --path ./path/to/local/manifest.yaml --output ./path/to/artifact.tgz\n\n  # List the files bundled in the artifact\n  tar -ztvf ./path/to/artifact.tgz\n`,\n\tRunE: buildArtifactCmdRun,\n}\n\ntype buildArtifactFlags struct {\n\toutput      string\n\tpath        string\n\tignorePaths []string\n}\n\nvar excludeOCI = append(strings.Split(sourceignore.ExcludeVCS, \",\"), strings.Split(sourceignore.ExcludeExt, \",\")...)\n\nvar buildArtifactArgs buildArtifactFlags\n\nfunc init() {\n\tbuildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.path, \"path\", \"p\", \"\", \"Path to the directory where the Kubernetes manifests are located.\")\n\tbuildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.output, \"output\", \"o\", \"artifact.tgz\", \"Path to where the artifact tgz file should be written.\")\n\tbuildArtifactCmd.Flags().StringSliceVar(&buildArtifactArgs.ignorePaths, \"ignore-paths\", excludeOCI, \"set paths to ignore in .gitignore format\")\n\n\tbuildCmd.AddCommand(buildArtifactCmd)\n}\n\nfunc buildArtifactCmdRun(cmd *cobra.Command, args []string) error {\n\tif buildArtifactArgs.path == \"\" {\n\t\treturn fmt.Errorf(\"invalid path %q\", buildArtifactArgs.path)\n\t}\n\n\tpath := buildArtifactArgs.path\n\tvar err error\n\tif buildArtifactArgs.path == \"-\" {\n\t\tpath, err = saveReaderToFile(os.Stdin)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdefer os.Remove(path)\n\t}\n\n\tif _, err := os.Stat(path); err != nil {\n\t\treturn fmt.Errorf(\"invalid path '%s', must point to an existing directory or file\", path)\n\t}\n\n\tlogger.Actionf(\"building artifact from %s\", path)\n\n\tociClient := oci.NewClient(oci.DefaultOptions())\n\tif err := ociClient.Build(buildArtifactArgs.output, path, buildArtifactArgs.ignorePaths); err != nil {\n\t\treturn fmt.Errorf(\"building artifact failed, error: %w\", err)\n\t}\n\n\tlogger.Successf(\"artifact created at %s\", buildArtifactArgs.output)\n\treturn nil\n}\n\nfunc saveReaderToFile(reader io.Reader) (string, error) {\n\tb, err := io.ReadAll(bufio.NewReader(reader))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tb = bytes.TrimRight(b, \"\\r\\n\")\n\tf, err := os.CreateTemp(\"\", \"*.yaml\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to create temp dir for stdin\")\n\t}\n\n\tdefer f.Close()\n\n\tif _, err := f.Write(b); err != nil {\n\t\treturn \"\", fmt.Errorf(\"error writing stdin to file: %w\", err)\n\t}\n\n\treturn f.Name(), nil\n}\n"
  },
  {
    "path": "cmd/flux/build_artifact_test.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc Test_saveReaderToFile(t *testing.T) {\n\tg := NewWithT(t)\n\n\ttestString := `apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: myapp\ndata:\n  foo: bar`\n\n\ttests := []struct {\n\t\tname      string\n\t\tstring    string\n\t\texpectErr bool\n\t}{\n\t\t{\n\t\t\tname:   \"yaml\",\n\t\t\tstring: testString,\n\t\t},\n\t\t{\n\t\t\tname:   \"yaml with carriage return\",\n\t\t\tstring: testString + \"\\r\\n\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttmpFile, err := saveReaderToFile(strings.NewReader(tt.string))\n\t\t\tg.Expect(err).To(BeNil())\n\n\t\t\tt.Cleanup(func() { _ = os.Remove(tmpFile) })\n\n\t\t\tb, err := os.ReadFile(tmpFile)\n\t\t\tif tt.expectErr {\n\t\t\t\tg.Expect(err).To(Not(BeNil()))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tg.Expect(err).To(BeNil())\n\t\t\tg.Expect(string(b)).To(BeEquivalentTo(testString))\n\t\t})\n\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/build_kustomization.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\n\t\"github.com/spf13/cobra\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tssautil \"github.com/fluxcd/pkg/ssa/utils\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/build\"\n)\n\nvar buildKsCmd = &cobra.Command{\n\tUse:     \"kustomization\",\n\tAliases: []string{\"ks\"},\n\tShort:   \"Build Kustomization\",\n\tLong: `The build command queries the Kubernetes API and fetches the specified Flux Kustomization. \nIt then uses the fetched in cluster flux kustomization to perform needed transformation on the local kustomization.yaml\npointed at by --path. The local kustomization.yaml is generated if it does not exist. Finally it builds the overlays using the local kustomization.yaml, and write the resulting multi-doc YAML to stdout.\n\nIt is possible to specify a Flux kustomization file using --kustomization-file.`,\n\tExample: `# Build the local manifests as they were built on the cluster\nflux build kustomization my-app --path ./path/to/local/manifests\n\n# Build using a local flux kustomization file\nflux build kustomization my-app --path ./path/to/local/manifests --kustomization-file ./path/to/local/my-app.yaml\n\n# Build in dry-run mode without connecting to the cluster.\n# Note that variable substitutions from Secrets and ConfigMaps are skipped in dry-run mode.\nflux build kustomization my-app --path ./path/to/local/manifests \\\n\t--kustomization-file ./path/to/local/my-app.yaml \\\n\t--dry-run\n\n# Exclude files by providing a comma separated list of entries that follow the .gitignore pattern fromat.\nflux build kustomization my-app --path ./path/to/local/manifests \\\n\t--kustomization-file ./path/to/local/my-app.yaml \\\n\t--ignore-paths \"/to_ignore/**/*.yaml,ignore.yaml\"\n\n# Run recursively on all encountered Kustomizations\nflux build kustomization my-app --path ./path/to/local/manifests \\\n\t--recursive \\\n\t--local-sources GitRepository/flux-system/my-repo=./path/to/local/git`,\n\tValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),\n\tRunE:              buildKsCmdRun,\n}\n\ntype buildKsFlags struct {\n\tkustomizationFile string\n\tpath              string\n\tignorePaths       []string\n\tdryRun            bool\n\tstrictSubst       bool\n\trecursive         bool\n\tlocalSources      map[string]string\n}\n\nvar buildKsArgs buildKsFlags\n\nfunc init() {\n\tbuildKsCmd.Flags().StringVar(&buildKsArgs.path, \"path\", \"\", \"Path to the manifests location.\")\n\tbuildKsCmd.Flags().StringVar(&buildKsArgs.kustomizationFile, \"kustomization-file\", \"\", \"Path to the Flux Kustomization YAML file.\")\n\tbuildKsCmd.Flags().StringSliceVar(&buildKsArgs.ignorePaths, \"ignore-paths\", nil, \"set paths to ignore in .gitignore format\")\n\tbuildKsCmd.Flags().BoolVar(&buildKsArgs.dryRun, \"dry-run\", false, \"Dry run mode.\")\n\tbuildKsCmd.Flags().BoolVar(&buildKsArgs.strictSubst, \"strict-substitute\", false,\n\t\t\"When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.\")\n\tbuildKsCmd.Flags().BoolVarP(&buildKsArgs.recursive, \"recursive\", \"r\", false, \"Recursively build Kustomizations\")\n\tbuildKsCmd.Flags().StringToStringVar(&buildKsArgs.localSources, \"local-sources\", nil, \"Comma-separated list of repositories in format: Kind/namespace/name=path\")\n\tbuildCmd.AddCommand(buildKsCmd)\n}\n\nfunc buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"%s name is required\", kustomizationType.humanKind)\n\t}\n\tname := args[0]\n\n\tif buildKsArgs.path == \"\" {\n\t\treturn fmt.Errorf(\"invalid resource path %q\", buildKsArgs.path)\n\t}\n\n\t// Normalize the path to handle Windows absolute and relative paths correctly\n\tbuildKsArgs.path, err = filepath.Abs(buildKsArgs.path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to resolve absolute path: %w\", err)\n\t}\n\tbuildKsArgs.path = filepath.Clean(buildKsArgs.path)\n\n\tif fs, err := os.Stat(buildKsArgs.path); err != nil || !fs.IsDir() {\n\t\treturn fmt.Errorf(\"invalid resource path %q\", buildKsArgs.path)\n\t}\n\n\tif buildKsArgs.dryRun && buildKsArgs.kustomizationFile == \"\" {\n\t\treturn fmt.Errorf(\"dry-run mode requires a kustomization file\")\n\t}\n\n\tif buildKsArgs.kustomizationFile != \"\" {\n\t\tif fs, err := os.Stat(buildKsArgs.kustomizationFile); os.IsNotExist(err) || fs.IsDir() {\n\t\t\treturn fmt.Errorf(\"invalid kustomization file %q\", buildKsArgs.kustomizationFile)\n\t\t}\n\t}\n\n\tvar builder *build.Builder\n\tif buildKsArgs.dryRun {\n\t\tbuilder, err = build.NewBuilder(name, buildKsArgs.path,\n\t\t\tbuild.WithTimeout(rootArgs.timeout),\n\t\t\tbuild.WithKustomizationFile(buildKsArgs.kustomizationFile),\n\t\t\tbuild.WithDryRun(buildKsArgs.dryRun),\n\t\t\tbuild.WithNamespace(*kubeconfigArgs.Namespace),\n\t\t\tbuild.WithIgnore(buildKsArgs.ignorePaths),\n\t\t\tbuild.WithStrictSubstitute(buildKsArgs.strictSubst),\n\t\t\tbuild.WithRecursive(buildKsArgs.recursive),\n\t\t\tbuild.WithLocalSources(buildKsArgs.localSources),\n\t\t)\n\t} else {\n\t\tbuilder, err = build.NewBuilder(name, buildKsArgs.path,\n\t\t\tbuild.WithClientConfig(kubeconfigArgs, kubeclientOptions),\n\t\t\tbuild.WithTimeout(rootArgs.timeout),\n\t\t\tbuild.WithKustomizationFile(buildKsArgs.kustomizationFile),\n\t\t\tbuild.WithIgnore(buildKsArgs.ignorePaths),\n\t\t\tbuild.WithStrictSubstitute(buildKsArgs.strictSubst),\n\t\t\tbuild.WithRecursive(buildKsArgs.recursive),\n\t\t\tbuild.WithLocalSources(buildKsArgs.localSources),\n\t\t)\n\t}\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// create a signal channel\n\tsigc := make(chan os.Signal, 1)\n\tsignal.Notify(sigc, os.Interrupt)\n\n\terrChan := make(chan error)\n\tgo func() {\n\t\tobjects, err := builder.Build()\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t}\n\n\t\tmanifests, err := ssautil.ObjectsToYAML(objects)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t}\n\n\t\tcmd.Print(manifests)\n\t\terrChan <- nil\n\t}()\n\n\tselect {\n\tcase <-sigc:\n\t\tfmt.Println(\"Build cancelled... exiting.\")\n\t\treturn builder.Cancel()\n\tcase err := <-errChan:\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n\n}\n"
  },
  {
    "path": "cmd/flux/build_kustomization_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"text/template\"\n)\n\nfunc setup(t *testing.T, tmpl map[string]string) {\n\tt.Helper()\n\ttestEnv.CreateObjectFile(\"./testdata/build-kustomization/podinfo-source.yaml\", tmpl, t)\n\ttestEnv.CreateObjectFile(\"./testdata/build-kustomization/podinfo-kustomization.yaml\", tmpl, t)\n}\n\nfunc TestBuildKustomization(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\targs       string\n\t\tresultFile string\n\t\tassertFunc string\n\t}{\n\t\t{\n\t\t\tname:       \"no args\",\n\t\t\targs:       \"build kustomization podinfo\",\n\t\t\tresultFile: \"invalid resource path \\\"\\\"\",\n\t\t\tassertFunc: \"assertError\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build podinfo\",\n\t\t\targs:       \"build kustomization podinfo --path ./testdata/build-kustomization/podinfo\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build podinfo without service\",\n\t\t\targs:       \"build kustomization podinfo --path ./testdata/build-kustomization/delete-service\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-without-service-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build deployment and configmap with var substitution\",\n\t\t\targs:       \"build kustomization podinfo --path ./testdata/build-kustomization/var-substitution\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build ignore\",\n\t\t\targs:       \"build kustomization podinfo --path ./testdata/build-kustomization/ignore --ignore-paths \\\"!configmap.yaml,!secret.yaml\\\"\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-with-ignore-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build with recursive\",\n\t\t\targs:       \"build kustomization podinfo --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-with-my-app-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t}\n\n\ttmpl := map[string]string{\n\t\t\"fluxns\": allocateNamespace(\"flux-system\"),\n\t}\n\tsetup(t, tmpl)\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar assert assertFunc\n\n\t\t\tswitch tt.assertFunc {\n\t\t\tcase \"assertGoldenTemplateFile\":\n\t\t\t\tassert = assertGoldenTemplateFile(tt.resultFile, tmpl)\n\t\t\tcase \"assertError\":\n\t\t\t\tassert = assertError(tt.resultFile)\n\t\t\t}\n\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args + \" -n \" + tmpl[\"fluxns\"],\n\t\t\t\tassert: assert,\n\t\t\t}\n\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n\nfunc TestBuildLocalKustomization(t *testing.T) {\n\tpodinfo := `apiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m0s\n  path: ./kustomize\n  force: true\n  prune: true\n  sourceRef:\n    kind: GitRepository\n    name: podinfo\n  targetNamespace: default\n  postBuild:\n    substitute:\n      cluster_env: \"prod\"\n      cluster_region: \"eu-central-1\"\n`\n\n\ttmpFile := filepath.Join(t.TempDir(), \"podinfo.yaml\")\n\n\ttests := []struct {\n\t\tname       string\n\t\targs       string\n\t\tresultFile string\n\t\tassertFunc string\n\t}{\n\t\t{\n\t\t\tname:       \"no args\",\n\t\t\targs:       \"build kustomization podinfo --kustomization-file ./wrongfile/ --path ./testdata/build-kustomization/podinfo\",\n\t\t\tresultFile: \"invalid kustomization file \\\"./wrongfile/\\\"\",\n\t\t\tassertFunc: \"assertError\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build podinfo\",\n\t\t\targs:       \"build kustomization podinfo --kustomization-file \" + tmpFile + \" --path ./testdata/build-kustomization/podinfo\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build podinfo without service\",\n\t\t\targs:       \"build kustomization podinfo --kustomization-file \" + tmpFile + \" --path ./testdata/build-kustomization/delete-service\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-without-service-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build deployment and configmap with var substitution\",\n\t\t\targs:       \"build kustomization podinfo --kustomization-file \" + tmpFile + \" --path ./testdata/build-kustomization/var-substitution\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build deployment and configmap with var substitution in dry-run mode\",\n\t\t\targs:       \"build kustomization podinfo --kustomization-file \" + tmpFile + \" --path ./testdata/build-kustomization/var-substitution --dry-run\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build with recursive\",\n\t\t\targs:       \"build kustomization podinfo --kustomization-file \" + tmpFile + \" --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-with-my-app-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build with recursive in dry-run mode\",\n\t\t\targs:       \"build kustomization podinfo --kustomization-file \" + tmpFile + \" --path ./testdata/build-kustomization/podinfo-with-my-app --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization --dry-run\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-with-my-app-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t}\n\n\ttmpl := map[string]string{\n\t\t\"fluxns\": allocateNamespace(\"flux-system\"),\n\t}\n\tsetup(t, tmpl)\n\n\ttemp, err := template.New(\"podinfo\").Parse(podinfo)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar b bytes.Buffer\n\terr = temp.Execute(&b, tmpl)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = os.WriteFile(tmpFile, b.Bytes(), 0666)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar assert assertFunc\n\n\t\t\tswitch tt.assertFunc {\n\t\t\tcase \"assertGoldenTemplateFile\":\n\t\t\t\tassert = assertGoldenTemplateFile(tt.resultFile, tmpl)\n\t\t\tcase \"assertError\":\n\t\t\t\tassert = assertError(tt.resultFile)\n\t\t\t}\n\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args + \" -n \" + tmpl[\"fluxns\"],\n\t\t\t\tassert: assert,\n\t\t\t}\n\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n\n// TestBuildKustomizationPathNormalization verifies that absolute and complex\n// paths are normalized to prevent path concatenation bugs (issue #5673).\n// Without normalization, paths could be duplicated like: /path/test/path/test/file\nfunc TestBuildKustomizationPathNormalization(t *testing.T) {\n\t// Get absolute path to testdata to test absolute path handling\n\tabsTestDataPath, err := filepath.Abs(\"testdata/build-kustomization/podinfo\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get absolute path: %v\", err)\n\t}\n\n\ttests := []struct {\n\t\tname       string\n\t\targs       string\n\t\tresultFile string\n\t\tassertFunc string\n\t}{\n\t\t{\n\t\t\tname:       \"build with absolute path\",\n\t\t\targs:       \"build kustomization podinfo --path \" + absTestDataPath,\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build with complex relative path (parent dir)\",\n\t\t\targs:       \"build kustomization podinfo --path ./testdata/build-kustomization/../build-kustomization/podinfo\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"build with path containing redundant separators\",\n\t\t\targs:       \"build kustomization podinfo --path ./testdata//build-kustomization//podinfo\",\n\t\t\tresultFile: \"./testdata/build-kustomization/podinfo-result.yaml\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t}\n\n\ttmpl := map[string]string{\n\t\t\"fluxns\": allocateNamespace(\"flux-system\"),\n\t}\n\tsetup(t, tmpl)\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar assert assertFunc\n\n\t\t\tswitch tt.assertFunc {\n\t\t\tcase \"assertGoldenTemplateFile\":\n\t\t\t\tassert = assertGoldenTemplateFile(tt.resultFile, tmpl)\n\t\t\tcase \"assertError\":\n\t\t\t\tassert = assertError(tt.resultFile)\n\t\t\t}\n\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args + \" -n \" + tmpl[\"fluxns\"],\n\t\t\t\tassert: assert,\n\t\t\t}\n\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/check.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\t\"github.com/spf13/cobra\"\n\tv1 \"k8s.io/api/apps/v1\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/pkg/version\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n\t\"github.com/fluxcd/flux2/v2/pkg/status\"\n)\n\nvar checkCmd = &cobra.Command{\n\tUse:   \"check\",\n\tArgs:  cobra.NoArgs,\n\tShort: \"Check requirements and installation\",\n\tLong: withPreviewNote(`The check command will perform a series of checks to validate that\nthe local environment is configured correctly and if the installed components are healthy.`),\n\tExample: `  # Run pre-installation checks\n  flux check --pre\n\n  # Run installation checks\n  flux check`,\n\tRunE: runCheckCmd,\n}\n\ntype checkFlags struct {\n\tpre             bool\n\tcomponents      []string\n\textraComponents []string\n\tpollInterval    time.Duration\n}\n\nvar kubernetesConstraints = []string{\n\t\">=1.33.0-0\",\n}\n\nvar checkArgs checkFlags\n\nfunc init() {\n\tcheckCmd.Flags().BoolVarP(&checkArgs.pre, \"pre\", \"\", false,\n\t\t\"only run pre-installation checks\")\n\tcheckCmd.Flags().StringSliceVar(&checkArgs.components, \"components\", rootArgs.defaults.Components,\n\t\t\"list of components, accepts comma-separated values\")\n\tcheckCmd.Flags().StringSliceVar(&checkArgs.extraComponents, \"components-extra\", nil,\n\t\t\"list of components in addition to those supplied or defaulted, accepts comma-separated values\")\n\tcheckCmd.Flags().DurationVar(&checkArgs.pollInterval, \"poll-interval\", 5*time.Second,\n\t\t\"how often the health checker should poll the cluster for the latest state of the resources.\")\n\trootCmd.AddCommand(checkCmd)\n}\n\nfunc runCheckCmd(cmd *cobra.Command, args []string) error {\n\tlogger.Actionf(\"checking prerequisites\")\n\tcheckFailed := false\n\n\tfluxCheck()\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tcfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Kubernetes client initialization failed: %s\", err.Error())\n\t}\n\n\tkubeClient, err := client.New(cfg, client.Options{Scheme: utils.NewScheme()})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !kubernetesCheck(cfg, kubernetesConstraints) {\n\t\tcheckFailed = true\n\t}\n\n\tif checkArgs.pre {\n\t\tif checkFailed {\n\t\t\tos.Exit(1)\n\t\t}\n\t\tlogger.Successf(\"prerequisites checks passed\")\n\t\treturn nil\n\t}\n\n\tlogger.Actionf(\"checking version in cluster\")\n\tif !fluxClusterVersionCheck(ctx, kubeClient) {\n\t\tcheckFailed = true\n\t}\n\n\tlogger.Actionf(\"checking controllers\")\n\tif !componentsCheck(ctx, kubeClient) {\n\t\tcheckFailed = true\n\t}\n\n\tlogger.Actionf(\"checking crds\")\n\tif !crdsCheck(ctx, kubeClient) {\n\t\tcheckFailed = true\n\t}\n\n\tif checkFailed {\n\t\tlogger.Failuref(\"check failed\")\n\t\tos.Exit(1)\n\t}\n\n\tlogger.Successf(\"all checks passed\")\n\treturn nil\n}\n\nfunc fluxCheck() {\n\tcurSv, err := version.ParseVersion(VERSION)\n\tif err != nil {\n\t\treturn\n\t}\n\t// Exclude development builds.\n\tif curSv.Prerelease() != \"\" {\n\t\treturn\n\t}\n\tlatest, err := install.GetLatestVersion()\n\tif err != nil {\n\t\treturn\n\t}\n\tlatestSv, err := version.ParseVersion(latest)\n\tif err != nil {\n\t\treturn\n\t}\n\tif latestSv.GreaterThan(curSv) {\n\t\tlogger.Failuref(\"flux %s <%s (new CLI version is available, please upgrade)\", curSv, latestSv)\n\t}\n}\n\nfunc kubernetesCheck(cfg *rest.Config, constraints []string) bool {\n\tclientSet, err := kubernetes.NewForConfig(cfg)\n\tif err != nil {\n\t\tlogger.Failuref(\"Kubernetes client initialization failed: %s\", err.Error())\n\t\treturn false\n\t}\n\n\tkv, err := clientSet.Discovery().ServerVersion()\n\tif err != nil {\n\t\tlogger.Failuref(\"Kubernetes API call failed: %s\", err.Error())\n\t\treturn false\n\t}\n\n\tv, err := version.ParseVersion(kv.String())\n\tif err != nil {\n\t\tlogger.Failuref(\"Kubernetes version can't be determined\")\n\t\treturn false\n\t}\n\n\tvar valid bool\n\tvar vrange string\n\tfor _, constraint := range constraints {\n\t\tc, _ := semver.NewConstraint(constraint)\n\t\tif c.Check(v) {\n\t\t\tvalid = true\n\t\t\tvrange = constraint\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !valid {\n\t\tlogger.Failuref(\"Kubernetes version %s does not match %s\", v.Original(), constraints[0])\n\t\treturn false\n\t}\n\n\tlogger.Successf(\"Kubernetes %s %s\", v.String(), vrange)\n\treturn true\n}\n\nfunc componentsCheck(ctx context.Context, kubeClient client.Client) bool {\n\tstatusChecker, err := status.NewStatusCheckerWithClient(kubeClient, checkArgs.pollInterval, rootArgs.timeout, logger)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tok := true\n\tselector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}\n\tvar list v1.DeploymentList\n\tns := *kubeconfigArgs.Namespace\n\tif err := kubeClient.List(ctx, &list, client.InNamespace(ns), selector); err == nil {\n\t\tif len(list.Items) == 0 {\n\t\t\tlogger.Failuref(\"no controllers found in the '%s' namespace with the label selector '%s=%s'\",\n\t\t\t\tns, manifestgen.PartOfLabelKey, manifestgen.PartOfLabelValue)\n\t\t\treturn false\n\t\t}\n\n\t\tfor _, d := range list.Items {\n\t\t\tif ref, err := buildComponentObjectRefs(d.Name); err == nil {\n\t\t\t\tif err := statusChecker.Assess(ref...); err != nil {\n\t\t\t\t\tok = false\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, c := range d.Spec.Template.Spec.Containers {\n\t\t\t\tlogger.Actionf(\"%s\", c.Image)\n\t\t\t}\n\t\t}\n\t}\n\treturn ok\n}\n\nfunc crdsCheck(ctx context.Context, kubeClient client.Client) bool {\n\tok := true\n\tselector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}\n\tvar list apiextensionsv1.CustomResourceDefinitionList\n\tif err := kubeClient.List(ctx, &list, client.InNamespace(*kubeconfigArgs.Namespace), selector); err == nil {\n\t\tif len(list.Items) == 0 {\n\t\t\tlogger.Failuref(\"no crds found with the label selector '%s=%s'\",\n\t\t\t\tmanifestgen.PartOfLabelKey, manifestgen.PartOfLabelValue)\n\t\t\treturn false\n\t\t}\n\n\t\tfor _, crd := range list.Items {\n\t\t\tversions := crd.Status.StoredVersions\n\t\t\tif len(versions) > 0 {\n\t\t\t\tlogger.Successf(\"%s\", crd.Name+\"/\"+versions[len(versions)-1])\n\t\t\t} else {\n\t\t\t\tok = false\n\t\t\t\tlogger.Failuref(\"no stored versions for %s\", crd.Name)\n\t\t\t}\n\t\t}\n\t}\n\treturn ok\n}\n\nfunc fluxClusterVersionCheck(ctx context.Context, kubeClient client.Client) bool {\n\tclusterInfo, err := getFluxClusterInfo(ctx, kubeClient)\n\tif err != nil {\n\t\tlogger.Failuref(\"checking failed: %s\", err.Error())\n\t\treturn false\n\t}\n\n\tif clusterInfo.distribution() != \"\" {\n\t\tlogger.Successf(\"distribution: %s\", clusterInfo.distribution())\n\t}\n\tlogger.Successf(\"bootstrapped: %t\", clusterInfo.bootstrapped)\n\treturn true\n}\n"
  },
  {
    "path": "cmd/flux/check_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nfunc TestCheckPre(t *testing.T) {\n\tjsonOutput, err := utils.ExecKubectlCommand(context.TODO(), utils.ModeCapture, *kubeconfigArgs.KubeConfig, *kubeconfigArgs.Context, \"version\", \"--output\", \"json\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error running utils.ExecKubectlCommand: %v\", err.Error())\n\t}\n\n\tvar versions map[string]interface{}\n\tif err := json.Unmarshal([]byte(jsonOutput), &versions); err != nil {\n\t\tt.Fatalf(\"Error unmarshalling '%s': %v\", jsonOutput, err.Error())\n\t}\n\n\tserverGitVersion := strings.TrimPrefix(\n\t\tversions[\"serverVersion\"].(map[string]interface{})[\"gitVersion\"].(string),\n\t\t\"v\")\n\n\tcmd := cmdTestCase{\n\t\targs: \"check --pre\",\n\t\tassert: assertGoldenTemplateFile(\"testdata/check/check_pre.golden\", map[string]string{\n\t\t\t\"serverVersion\": serverGitVersion,\n\t\t}),\n\t}\n\tcmd.runTestCmd(t)\n}\n"
  },
  {
    "path": "cmd/flux/cluster_info.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/manifoldco/promptui\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n)\n\n// bootstrapLabels are labels put on a resource by kustomize-controller. These labels on the CRD indicates\n// that flux has been bootstrapped.\nvar bootstrapLabels = []string{\n\tfmt.Sprintf(\"%s/name\", kustomizev1.GroupVersion.Group),\n\tfmt.Sprintf(\"%s/namespace\", kustomizev1.GroupVersion.Group),\n}\n\n// fluxClusterInfo contains information about an existing flux installation on a cluster.\ntype fluxClusterInfo struct {\n\t// bootstrapped indicates that Flux was installed using the `flux bootstrap` command.\n\tbootstrapped bool\n\t// managedBy is the name of the tool being used to manage the installation of Flux.\n\tmanagedBy string\n\t// partOf indicates which distribution the instance is a part of.\n\tpartOf string\n\t// version is the Flux version number in semver format.\n\tversion string\n}\n\n// getFluxClusterInfo returns information on the Flux installation running on the cluster.\n// If an error occurred, the returned error will be non-nil.\n//\n// This function retrieves the GitRepository CRD from the cluster and checks it\n// for a set of labels used to determine the Flux version and how Flux was installed.\n// It returns the NotFound error from the underlying library if it was unable to find\n// the GitRepository CRD and this can be used to check if Flux is installed.\nfunc getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, error) {\n\tvar info fluxClusterInfo\n\tcrdMetadata := &metav1.PartialObjectMetadata{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tAPIVersion: apiextensionsv1.SchemeGroupVersion.String(),\n\t\t\tKind:       \"CustomResourceDefinition\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: fmt.Sprintf(\"gitrepositories.%s\", sourcev1.GroupVersion.Group),\n\t\t},\n\t}\n\tif err := c.Get(ctx, client.ObjectKeyFromObject(crdMetadata), crdMetadata); err != nil {\n\t\treturn info, err\n\t}\n\n\tinfo.version = crdMetadata.Labels[manifestgen.VersionLabelKey]\n\n\tvar present bool\n\tfor _, l := range bootstrapLabels {\n\t\t_, present = crdMetadata.Labels[l]\n\t}\n\tif present {\n\t\tinfo.bootstrapped = true\n\t}\n\n\t// the `app.kubernetes.io/managed-by` label is not set by flux but might be set by other\n\t// tools used to install Flux e.g Helm.\n\tif manager, ok := crdMetadata.Labels[\"app.kubernetes.io/managed-by\"]; ok {\n\t\tinfo.managedBy = manager\n\t}\n\n\tif partOf, ok := crdMetadata.Labels[manifestgen.PartOfLabelKey]; ok {\n\t\tinfo.partOf = partOf\n\t}\n\treturn info, nil\n}\n\n// confirmFluxInstallOverride displays a prompt to the user so that they can confirm before overriding\n// a Flux installation. It returns nil if the installation should continue,\n// promptui.ErrAbort if the user doesn't confirm, or an error encountered.\nfunc confirmFluxInstallOverride(info fluxClusterInfo) error {\n\t// no need to display prompt if installation is managed by Flux\n\tif installManagedByFlux(info.managedBy) {\n\t\treturn nil\n\t}\n\n\tdisplay := fmt.Sprintf(\"Flux %s has been installed on this cluster with %s!\", info.version, info.managedBy)\n\tfmt.Fprintln(rootCmd.ErrOrStderr(), display)\n\tprompt := promptui.Prompt{\n\t\tLabel:     fmt.Sprintf(\"Are you sure you want to override the %s installation? Y/N\", info.managedBy),\n\t\tIsConfirm: true,\n\t}\n\t_, err := prompt.Run()\n\treturn err\n}\n\nfunc (info fluxClusterInfo) distribution() string {\n\tdistribution := info.version\n\tif info.partOf != \"\" {\n\t\tdistribution = fmt.Sprintf(\"%s-%s\", info.partOf, info.version)\n\t}\n\treturn distribution\n}\n\nfunc installManagedByFlux(manager string) bool {\n\treturn manager == \"\" || manager == \"flux\"\n}\n"
  },
  {
    "path": "cmd/flux/cluster_info_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t. \"github.com/onsi/gomega\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client/fake\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tssautil \"github.com/fluxcd/pkg/ssa/utils\"\n)\n\nfunc Test_getFluxClusterInfo(t *testing.T) {\n\tg := NewWithT(t)\n\tf, err := os.Open(\"./testdata/cluster_info/gitrepositories.yaml\")\n\tg.Expect(err).To(BeNil())\n\n\tobjs, err := ssautil.ReadObjects(f)\n\tg.Expect(err).To(Not(HaveOccurred()))\n\tgitrepo := objs[0]\n\n\ttests := []struct {\n\t\tname     string\n\t\tlabels   map[string]string\n\t\twantErr  bool\n\t\twantInfo fluxClusterInfo\n\t}{\n\t\t{\n\t\t\tname:    \"no git repository CRD present\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"CRD with kustomize-controller labels\",\n\t\t\tlabels: map[string]string{\n\t\t\t\tfmt.Sprintf(\"%s/name\", kustomizev1.GroupVersion.Group):      \"flux-system\",\n\t\t\t\tfmt.Sprintf(\"%s/namespace\", kustomizev1.GroupVersion.Group): \"flux-system\",\n\t\t\t\t\"app.kubernetes.io/version\":                                 \"v2.1.0\",\n\t\t\t},\n\t\t\twantInfo: fluxClusterInfo{\n\t\t\t\tversion:      \"v2.1.0\",\n\t\t\t\tbootstrapped: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"CRD with kustomize-controller labels and managed-by label\",\n\t\t\tlabels: map[string]string{\n\t\t\t\tfmt.Sprintf(\"%s/name\", kustomizev1.GroupVersion.Group):      \"flux-system\",\n\t\t\t\tfmt.Sprintf(\"%s/namespace\", kustomizev1.GroupVersion.Group): \"flux-system\",\n\t\t\t\t\"app.kubernetes.io/version\":                                 \"v2.1.0\",\n\t\t\t\t\"app.kubernetes.io/managed-by\":                              \"flux\",\n\t\t\t},\n\t\t\twantInfo: fluxClusterInfo{\n\t\t\t\tversion:      \"v2.1.0\",\n\t\t\t\tbootstrapped: true,\n\t\t\t\tmanagedBy:    \"flux\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"CRD with only managed-by label\",\n\t\t\tlabels: map[string]string{\n\t\t\t\t\"app.kubernetes.io/version\":    \"v2.1.0\",\n\t\t\t\t\"app.kubernetes.io/managed-by\": \"helm\",\n\t\t\t},\n\t\t\twantInfo: fluxClusterInfo{\n\t\t\t\tversion:   \"v2.1.0\",\n\t\t\t\tmanagedBy: \"helm\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"CRD with no labels\",\n\t\t\tlabels:   map[string]string{},\n\t\t\twantInfo: fluxClusterInfo{},\n\t\t},\n\t\t{\n\t\t\tname: \"CRD with only version label\",\n\t\t\tlabels: map[string]string{\n\t\t\t\t\"app.kubernetes.io/version\": \"v2.1.0\",\n\t\t\t},\n\t\t\twantInfo: fluxClusterInfo{\n\t\t\t\tversion: \"v2.1.0\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"CRD with version and part-of labels\",\n\t\t\tlabels: map[string]string{\n\t\t\t\t\"app.kubernetes.io/version\": \"v2.1.0\",\n\t\t\t\t\"app.kubernetes.io/part-of\": \"flux\",\n\t\t\t},\n\t\t\twantInfo: fluxClusterInfo{\n\t\t\t\tversion: \"v2.1.0\",\n\t\t\t\tpartOf:  \"flux\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tg := NewWithT(t)\n\t\t\tnewscheme := runtime.NewScheme()\n\t\t\tapiextensionsv1.AddToScheme(newscheme)\n\t\t\tbuilder := fake.NewClientBuilder().WithScheme(newscheme)\n\t\t\tif tt.labels != nil {\n\t\t\t\tgitrepo.SetLabels(tt.labels)\n\t\t\t\tbuilder = builder.WithRuntimeObjects(gitrepo)\n\t\t\t}\n\n\t\t\tclient := builder.Build()\n\t\t\tinfo, err := getFluxClusterInfo(context.Background(), client)\n\t\t\tif tt.wantErr {\n\t\t\t\tg.Expect(err).To(HaveOccurred())\n\t\t\t\tg.Expect(errors.IsNotFound(err)).To(BeTrue())\n\t\t\t} else {\n\t\t\t\tg.Expect(err).To(Not(HaveOccurred()))\n\t\t\t}\n\n\t\t\tg.Expect(info).To(BeEquivalentTo(tt.wantInfo))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/completion.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/dynamic\"\n)\n\nvar completionCmd = &cobra.Command{\n\tUse:   \"completion\",\n\tShort: \"Generates completion scripts for various shells\",\n\tLong:  `The completion sub-command generates completion scripts for various shells.`,\n}\n\nfunc init() {\n\trootCmd.AddCommand(completionCmd)\n}\n\nfunc contextsCompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\trawConfig, err := kubeconfigArgs.ToRawKubeConfigLoader().RawConfig()\n\tif err != nil {\n\t\treturn completionError(err)\n\t}\n\n\tvar comps []string\n\n\tfor name := range rawConfig.Contexts {\n\t\tif strings.HasPrefix(name, toComplete) {\n\t\t\tcomps = append(comps, name)\n\t\t}\n\t}\n\n\treturn comps, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc resourceNamesCompletionFunc(gvk schema.GroupVersionKind) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\t\tdefer cancel()\n\n\t\tcfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)\n\t\tif err != nil {\n\t\t\treturn completionError(err)\n\t\t}\n\n\t\tmapper, err := kubeconfigArgs.ToRESTMapper()\n\t\tif err != nil {\n\t\t\treturn completionError(err)\n\t\t}\n\n\t\tmapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)\n\t\tif err != nil {\n\t\t\treturn completionError(err)\n\t\t}\n\n\t\tclient, err := dynamic.NewForConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn completionError(err)\n\t\t}\n\n\t\tvar dr dynamic.ResourceInterface\n\t\tif mapping.Scope.Name() == meta.RESTScopeNameNamespace {\n\t\t\tdr = client.Resource(mapping.Resource).Namespace(*kubeconfigArgs.Namespace)\n\t\t} else {\n\t\t\tdr = client.Resource(mapping.Resource)\n\t\t}\n\n\t\tlist, err := dr.List(ctx, metav1.ListOptions{})\n\t\tif err != nil {\n\t\t\treturn completionError(err)\n\t\t}\n\n\t\tvar comps []string\n\n\t\tfor _, item := range list.Items {\n\t\t\tname := item.GetName()\n\n\t\t\tif strings.HasPrefix(name, toComplete) {\n\t\t\t\tcomps = append(comps, name)\n\t\t\t}\n\t\t}\n\n\t\treturn comps, cobra.ShellCompDirectiveNoFileComp\n\t}\n}\n\nfunc completionError(err error) ([]string, cobra.ShellCompDirective) {\n\tcobra.CompError(err.Error())\n\treturn nil, cobra.ShellCompDirectiveError\n}\n"
  },
  {
    "path": "cmd/flux/completion_bash.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar completionBashCmd = &cobra.Command{\n\tUse:   \"bash\",\n\tShort: \"Generates bash completion scripts\",\n\tLong:  `The completion sub-command generates completion scripts for bash.`,\n\tExample: `To load completion run\n\n. <(flux completion bash)\n\nTo configure your bash shell to load completions for each session add to your bashrc\n\n# ~/.bashrc or ~/.profile\ncommand -v flux >/dev/null && . <(flux completion bash)`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\trootCmd.GenBashCompletion(os.Stdout)\n\t},\n}\n\nfunc init() {\n\tcompletionCmd.AddCommand(completionBashCmd)\n}\n"
  },
  {
    "path": "cmd/flux/completion_fish.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar completionFishCmd = &cobra.Command{\n\tUse:   \"fish\",\n\tShort: \"Generates fish completion scripts\",\n\tLong:  `The completion sub-command generates completion scripts for fish.`,\n\tExample: `To configure your fish shell to load completions for each session write this script to your completions dir:\n\nflux completion fish > ~/.config/fish/completions/flux.fish\n\nSee http://fishshell.com/docs/current/index.html#completion-own for more details`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\trootCmd.GenFishCompletion(os.Stdout, true)\n\t},\n}\n\nfunc init() {\n\tcompletionCmd.AddCommand(completionFishCmd)\n}\n"
  },
  {
    "path": "cmd/flux/completion_powershell.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar completionPowerShellCmd = &cobra.Command{\n\tUse:   \"powershell\",\n\tShort: \"Generates powershell completion scripts\",\n\tLong:  `The completion sub-command generates completion scripts for powershell.`,\n\tExample: `To load completion run\n\n. <(flux completion powershell)\n\nTo configure your powershell shell to load completions for each session add to your powershell profile\n\nWindows:\n\ncd \"$env:USERPROFILE\\Documents\\WindowsPowerShell\\Modules\"\nflux completion powershell >> flux-completion.ps1\n\nLinux:\n\ncd \"${XDG_CONFIG_HOME:-\"$HOME/.config/\"}/powershell/modules\"\nflux completion powershell >> flux-completions.ps1`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\trootCmd.GenPowerShellCompletion(os.Stdout)\n\t},\n}\n\nfunc init() {\n\tcompletionCmd.AddCommand(completionPowerShellCmd)\n}\n"
  },
  {
    "path": "cmd/flux/completion_zsh.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar completionZshCmd = &cobra.Command{\n\tUse:   \"zsh\",\n\tShort: \"Generates zsh completion scripts\",\n\tLong:  `The completion sub-command generates completion scripts for zsh.`,\n\tExample: `To load completion run\n\n. <(flux completion zsh)\n\nTo configure your zsh shell to load completions for each session add to your zshrc\n\n# ~/.zshrc or ~/.profile\ncommand -v flux >/dev/null && . <(flux completion zsh)\n\nor write a cached file in one of the completion directories in your ${fpath}:\n\necho \"${fpath// /\\n}\" | grep -i completion\nflux completion zsh > _flux\n\nmv _flux ~/.oh-my-zsh/completions  # oh-my-zsh\nmv _flux ~/.zprezto/modules/completion/external/src/  # zprezto`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\trootCmd.GenZshCompletion(os.Stdout)\n\t\t// Cobra doesn't source zsh completion file, explicitly doing it here\n\t\tfmt.Println(\"compdef _flux flux\")\n\t},\n}\n\nfunc init() {\n\tcompletionCmd.AddCommand(completionZshCmd)\n}\n"
  },
  {
    "path": "cmd/flux/create.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar createCmd = &cobra.Command{\n\tUse:   \"create\",\n\tShort: \"Create or update sources and resources\",\n\tLong:  `The create sub-commands generate sources and resources.`,\n}\n\ntype createFlags struct {\n\tinterval time.Duration\n\texport   bool\n\tlabels   []string\n}\n\nvar createArgs createFlags\n\nfunc init() {\n\tcreateCmd.PersistentFlags().DurationVarP(&createArgs.interval, \"interval\", \"\", time.Minute, \"source sync interval\")\n\tcreateCmd.PersistentFlags().BoolVar(&createArgs.export, \"export\", false, \"export in YAML format to stdout\")\n\tcreateCmd.PersistentFlags().StringSliceVar(&createArgs.labels, \"label\", nil,\n\t\t\"set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)\")\n\tcreateCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {\n\t\tif len(args) < 1 {\n\t\t\treturn fmt.Errorf(\"name is required\")\n\t\t}\n\n\t\tname := args[0]\n\t\tif !validateObjectName(name) {\n\t\t\treturn fmt.Errorf(\"name '%s' is invalid, it should adhere to standard defined in RFC 1123, the name can only contain alphanumeric characters or '-'\", name)\n\t\t}\n\n\t\treturn nil\n\t}\n\trootCmd.AddCommand(createCmd)\n}\n\n// upsertable is an interface for values that can be used in `upsert`.\ntype upsertable interface {\n\tadapter\n\tnamed\n}\n\n// upsert updates or inserts an object. Instead of providing the\n// object itself, you provide a named (as in Name and Namespace)\n// template value, and a mutate function which sets the values you\n// want to update. The mutate function is nullary -- you mutate a\n// value in the closure, e.g., by doing this:\n//\n//\tvar existing Value\n//\texisting.Name = name\n//\texisting.Namespace = ns\n//\tupsert(ctx, client, valueAdapter{&value}, func() error {\n//\t  value.Spec = onePreparedEarlier\n//\t})\nfunc (names apiType) upsert(ctx context.Context, kubeClient client.Client, object upsertable, mutate func() error) (types.NamespacedName, error) {\n\tnsname := types.NamespacedName{\n\t\tNamespace: object.GetNamespace(),\n\t\tName:      object.GetName(),\n\t}\n\n\top, err := controllerutil.CreateOrUpdate(ctx, kubeClient, object.asClientObject(), mutate)\n\tif err != nil {\n\t\treturn nsname, err\n\t}\n\n\tswitch op {\n\tcase controllerutil.OperationResultCreated:\n\t\tlogger.Successf(\"%s created\", names.kind)\n\tcase controllerutil.OperationResultUpdated:\n\t\tlogger.Successf(\"%s updated\", names.kind)\n\t}\n\treturn nsname, nil\n}\n\ntype upsertWaitable interface {\n\tupsertable\n\tstatusable\n}\n\n// upsertAndWait encodes the pattern of creating or updating a\n// resource, then waiting for it to reconcile. See the note on\n// `upsert` for how to work with the `mutate` argument.\nfunc (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) error {\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) // NB globals\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Generatef(\"generating %s\", names.kind)\n\tlogger.Actionf(\"applying %s\", names.kind)\n\n\tnamespacedName, err := names.upsert(ctx, kubeClient, object, mutate)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Waitingf(\"waiting for %s reconciliation\", names.kind)\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,\n\t\tisObjectReadyConditionFunc(kubeClient, namespacedName, object.asClientObject())); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"%s reconciliation completed\", names.kind)\n\treturn nil\n}\n\nfunc parseLabels() (map[string]string, error) {\n\tresult := make(map[string]string)\n\tfor _, label := range createArgs.labels {\n\t\t// validate key value pair\n\t\tparts := strings.Split(label, \"=\")\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, fmt.Errorf(\"invalid label format '%s', must be key=value\", label)\n\t\t}\n\n\t\t// validate label name\n\t\tif errors := validation.IsQualifiedName(parts[0]); len(errors) > 0 {\n\t\t\treturn nil, fmt.Errorf(\"invalid label '%s': %v\", parts[0], errors)\n\t\t}\n\n\t\t// validate label value\n\t\tif errors := validation.IsValidLabelValue(parts[1]); len(errors) > 0 {\n\t\t\treturn nil, fmt.Errorf(\"invalid label value '%s': %v\", parts[1], errors)\n\t\t}\n\n\t\tresult[parts[0]] = parts[1]\n\t}\n\n\treturn result, nil\n}\n\nfunc validateObjectName(name string) bool {\n\tr := regexp.MustCompile(`^[a-z0-9]([a-z0-9\\-]){0,61}[a-z0-9]$`)\n\treturn r.MatchString(name)\n}\n"
  },
  {
    "path": "cmd/flux/create_alert.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n\tnotificationv1b3 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar createAlertCmd = &cobra.Command{\n\tUse:   \"alert [name]\",\n\tShort: \"Create or update a Alert resource\",\n\tLong:  withPreviewNote(`The create alert command generates a Alert resource.`),\n\tExample: `  # Create an Alert for kustomization events\n  flux create alert \\\n  --event-severity info \\\n  --event-source Kustomization/flux-system \\\n  --provider-ref slack \\\n  flux-system`,\n\tRunE: createAlertCmdRun,\n}\n\ntype alertFlags struct {\n\tproviderRef   string\n\teventSeverity string\n\teventSources  []string\n}\n\nvar alertArgs alertFlags\n\nfunc init() {\n\tcreateAlertCmd.Flags().StringVar(&alertArgs.providerRef, \"provider-ref\", \"\", \"reference to provider\")\n\tcreateAlertCmd.Flags().StringVar(&alertArgs.eventSeverity, \"event-severity\", \"\", \"severity of events to send alerts for\")\n\tcreateAlertCmd.Flags().StringSliceVar(&alertArgs.eventSources, \"event-source\", []string{}, \"sources that should generate alerts (<kind>/<name>), also accepts comma-separated values\")\n\tcreateCmd.AddCommand(createAlertCmd)\n}\n\nfunc createAlertCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tif alertArgs.providerRef == \"\" {\n\t\treturn fmt.Errorf(\"provider ref is required\")\n\t}\n\n\teventSources := []notificationv1.CrossNamespaceObjectReference{}\n\tfor _, eventSource := range alertArgs.eventSources {\n\t\tkind, name, namespace := utils.ParseObjectKindNameNamespace(eventSource)\n\t\tif kind == \"\" {\n\t\t\treturn fmt.Errorf(\"invalid event source '%s', must be in format <kind>/<name>\", eventSource)\n\t\t}\n\n\t\teventSources = append(eventSources, notificationv1.CrossNamespaceObjectReference{\n\t\t\tKind:      kind,\n\t\t\tName:      name,\n\t\t\tNamespace: namespace,\n\t\t})\n\t}\n\n\tif len(eventSources) == 0 {\n\t\treturn fmt.Errorf(\"at least one event source is required\")\n\t}\n\n\tsourceLabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !createArgs.export {\n\t\tlogger.Generatef(\"generating Alert\")\n\t}\n\n\talert := notificationv1b3.Alert{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    sourceLabels,\n\t\t},\n\t\tSpec: notificationv1b3.AlertSpec{\n\t\t\tProviderRef: meta.LocalObjectReference{\n\t\t\t\tName: alertArgs.providerRef,\n\t\t\t},\n\t\t\tEventSeverity: alertArgs.eventSeverity,\n\t\t\tEventSources:  eventSources,\n\t\t\tSuspend:       false,\n\t\t},\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportAlert(&alert))\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"applying Alert\")\n\tnamespacedName, err := upsertAlert(ctx, kubeClient, &alert)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Waitingf(\"waiting for Alert reconciliation\")\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,\n\t\tisStaticObjectReadyConditionFunc(kubeClient, namespacedName, &alert)); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"Alert %s is ready\", name)\n\treturn nil\n}\n\nfunc upsertAlert(ctx context.Context, kubeClient client.Client,\n\talert *notificationv1b3.Alert) (types.NamespacedName, error) {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: alert.GetNamespace(),\n\t\tName:      alert.GetName(),\n\t}\n\n\tvar existing notificationv1b3.Alert\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, alert); err != nil {\n\t\t\t\treturn namespacedName, err\n\t\t\t} else {\n\t\t\t\tlogger.Successf(\"Alert created\")\n\t\t\t\treturn namespacedName, nil\n\t\t\t}\n\t\t}\n\t\treturn namespacedName, err\n\t}\n\n\texisting.Labels = alert.Labels\n\texisting.Spec = alert.Spec\n\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\treturn namespacedName, err\n\t}\n\talert = &existing\n\tlogger.Successf(\"Alert updated\")\n\treturn namespacedName, nil\n}\n"
  },
  {
    "path": "cmd/flux/create_alertprovider.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar createAlertProviderCmd = &cobra.Command{\n\tUse:   \"alert-provider [name]\",\n\tShort: \"Create or update a Provider resource\",\n\tLong:  withPreviewNote(`The create alert-provider command generates a Provider resource.`),\n\tExample: `  # Create a Provider for a Slack channel\n  flux create alert-provider slack \\\n  --type slack \\\n  --channel general \\\n  --address https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \\\n  --secret-ref webhook-url\n\n  # Create a Provider for a Github repository\n  flux create alert-provider github-podinfo \\\n  --type github \\\n  --address https://github.com/stefanprodan/podinfo \\\n  --secret-ref github-token`,\n\tRunE: createAlertProviderCmdRun,\n}\n\ntype alertProviderFlags struct {\n\talertType string\n\tchannel   string\n\tusername  string\n\taddress   string\n\tsecretRef string\n}\n\nvar alertProviderArgs alertProviderFlags\n\nfunc init() {\n\tcreateAlertProviderCmd.Flags().StringVar(&alertProviderArgs.alertType, \"type\", \"\", \"type of provider\")\n\tcreateAlertProviderCmd.Flags().StringVar(&alertProviderArgs.channel, \"channel\", \"\", \"channel to send messages to in the case of a chat provider\")\n\tcreateAlertProviderCmd.Flags().StringVar(&alertProviderArgs.username, \"username\", \"\", \"bot username used by the provider\")\n\tcreateAlertProviderCmd.Flags().StringVar(&alertProviderArgs.address, \"address\", \"\", \"path to either the git repository, chat provider or webhook\")\n\tcreateAlertProviderCmd.Flags().StringVar(&alertProviderArgs.secretRef, \"secret-ref\", \"\", \"name of secret containing authentication token\")\n\tcreateCmd.AddCommand(createAlertProviderCmd)\n}\n\nfunc createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tif alertProviderArgs.alertType == \"\" {\n\t\treturn fmt.Errorf(\"Provider type is required\")\n\t}\n\n\tsourceLabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !createArgs.export {\n\t\tlogger.Generatef(\"generating Provider\")\n\t}\n\n\tprovider := notificationv1.Provider{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    sourceLabels,\n\t\t},\n\t\tSpec: notificationv1.ProviderSpec{\n\t\t\tType:     alertProviderArgs.alertType,\n\t\t\tChannel:  alertProviderArgs.channel,\n\t\t\tUsername: alertProviderArgs.username,\n\t\t\tAddress:  alertProviderArgs.address,\n\t\t},\n\t}\n\n\tif alertProviderArgs.secretRef != \"\" {\n\t\tprovider.Spec.SecretRef = &meta.LocalObjectReference{\n\t\t\tName: alertProviderArgs.secretRef,\n\t\t}\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportAlertProvider(&provider))\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"applying Provider\")\n\tnamespacedName, err := upsertAlertProvider(ctx, kubeClient, &provider)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Waitingf(\"waiting for Provider reconciliation\")\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,\n\t\tisStaticObjectReadyConditionFunc(kubeClient, namespacedName, &provider)); err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Successf(\"Provider %s is ready\", name)\n\n\treturn nil\n}\n\nfunc upsertAlertProvider(ctx context.Context, kubeClient client.Client,\n\tprovider *notificationv1.Provider) (types.NamespacedName, error) {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: provider.GetNamespace(),\n\t\tName:      provider.GetName(),\n\t}\n\n\tvar existing notificationv1.Provider\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, provider); err != nil {\n\t\t\t\treturn namespacedName, err\n\t\t\t} else {\n\t\t\t\tlogger.Successf(\"Provider created\")\n\t\t\t\treturn namespacedName, nil\n\t\t\t}\n\t\t}\n\t\treturn namespacedName, err\n\t}\n\n\texisting.Labels = provider.Labels\n\texisting.Spec = provider.Spec\n\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\treturn namespacedName, err\n\t}\n\tprovider = &existing\n\tlogger.Successf(\"Provider updated\")\n\treturn namespacedName, nil\n}\n"
  },
  {
    "path": "cmd/flux/create_helmrelease.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/yaml\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\t\"github.com/fluxcd/pkg/runtime/transform\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar createHelmReleaseCmd = &cobra.Command{\n\tUse:     \"helmrelease [name]\",\n\tAliases: []string{\"hr\"},\n\tShort:   \"Create or update a HelmRelease resource\",\n\tLong:    `The helmrelease create command generates a HelmRelease resource for a given HelmRepository source.`,\n\tExample: `  # Create a HelmRelease with a chart from a HelmRepository source\n  flux create hr podinfo \\\n    --interval=10m \\\n    --source=HelmRepository/podinfo \\\n    --chart=podinfo \\\n    --chart-version=\">4.0.0\"\n\n  # Create a HelmRelease with a chart from a GitRepository source\n  flux create hr podinfo \\\n    --interval=10m \\\n    --source=GitRepository/podinfo \\\n    --chart=./charts/podinfo\n\n  # Create a HelmRelease with a chart from a Bucket source\n  flux create hr podinfo \\\n    --interval=10m \\\n    --source=Bucket/podinfo \\\n    --chart=./charts/podinfo\n\n  # Create a HelmRelease with values from local YAML files\n  flux create hr podinfo \\\n    --source=HelmRepository/podinfo \\\n    --chart=podinfo \\\n    --values=./my-values1.yaml \\\n    --values=./my-values2.yaml\n\n  # Create a HelmRelease with values from a Kubernetes secret\n  kubectl -n app create secret generic my-secret-values \\\n\t--from-file=values.yaml=/path/to/my-secret-values.yaml\n  flux -n app create hr podinfo \\\n    --source=HelmRepository/podinfo \\\n    --chart=podinfo \\\n    --values-from=Secret/my-secret-values\n\n  # Create a HelmRelease with a custom release name\n  flux create hr podinfo \\\n    --release-name=podinfo-dev \\\n    --source=HelmRepository/podinfo \\\n    --chart=podinfo\n\n  # Create a HelmRelease targeting another namespace than the resource\n  flux create hr podinfo \\\n    --target-namespace=test \\\n    --create-target-namespace=true \\\n    --source=HelmRepository/podinfo \\\n    --chart=podinfo\n\n  # Create a HelmRelease with custom storage namespace for hub-and-spoke model\n  flux create hr podinfo \\\n    --target-namespace=production \\\n    --storage-namespace=fluxcd-system \\\n    --source=HelmRepository/podinfo \\\n    --chart=podinfo\n\n  # Create a HelmRelease using a source from a different namespace\n  flux create hr podinfo \\\n    --namespace=default \\\n    --source=HelmRepository/podinfo.flux-system \\\n    --chart=podinfo\n\n  # Create a HelmRelease definition on disk without applying it on the cluster\n  flux create hr podinfo \\\n    --source=HelmRepository/podinfo \\\n    --chart=podinfo \\\n    --values=./values.yaml \\\n    --export > podinfo-release.yaml\n\t\t\n  # Create a HelmRelease using a chart from a HelmChart resource\n  flux create hr podinfo \\\n    --namespace=default \\\n    --chart-ref=HelmChart/podinfo.flux-system \\\n\n  # Create a HelmRelease using a chart from an OCIRepository resource\n  flux create hr podinfo \\\n    --namespace=default \\\n    --chart-ref=OCIRepository/podinfo.flux-system`,\n\tRunE: createHelmReleaseCmdRun,\n}\n\ntype helmReleaseFlags struct {\n\tname                string\n\tsource              flags.HelmChartSource\n\tdependsOn           []string\n\tchart               string\n\tchartVersion        string\n\tchartRef            string\n\ttargetNamespace     string\n\tstorageNamespace    string\n\tcreateNamespace     bool\n\tvaluesFiles         []string\n\tvaluesFrom          []string\n\tsaName              string\n\tcrds                flags.CRDsPolicy\n\treconcileStrategy   string\n\tchartInterval       time.Duration\n\tkubeConfigSecretRef string\n}\n\nvar helmReleaseArgs helmReleaseFlags\n\nvar supportedHelmReleaseValuesFromKinds = []string{\"Secret\", \"ConfigMap\"}\n\nvar supportedHelmReleaseReferenceKinds = []string{sourcev1.OCIRepositoryKind, sourcev1.HelmChartKind}\n\nfunc init() {\n\tcreateHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.name, \"release-name\", \"\", \"name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'\")\n\tcreateHelmReleaseCmd.Flags().Var(&helmReleaseArgs.source, \"source\", helmReleaseArgs.source.Description())\n\tcreateHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.chart, \"chart\", \"\", \"Helm chart name or path\")\n\tcreateHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.chartVersion, \"chart-version\", \"\", \"Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)\")\n\tcreateHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.dependsOn, \"depends-on\", nil, \"HelmReleases that must be ready before this release can be installed, supported formats '<name>' and '<namespace>/<name>'\")\n\tcreateHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.targetNamespace, \"target-namespace\", \"\", \"namespace to install this release, defaults to the HelmRelease namespace\")\n\tcreateHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.storageNamespace, \"storage-namespace\", \"\", \"namespace to store the Helm release, defaults to the target namespace\")\n\tcreateHelmReleaseCmd.Flags().BoolVar(&helmReleaseArgs.createNamespace, \"create-target-namespace\", false, \"create the target namespace if it does not exist\")\n\tcreateHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.saName, \"service-account\", \"\", \"the name of the service account to impersonate when reconciling this HelmRelease\")\n\tcreateHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.reconcileStrategy, \"reconcile-strategy\", \"ChartVersion\", \"the reconcile strategy for helm chart created by the helm release(accepted values: Revision and ChartRevision)\")\n\tcreateHelmReleaseCmd.Flags().DurationVarP(&helmReleaseArgs.chartInterval, \"chart-interval\", \"\", 0, \"the interval of which to check for new chart versions\")\n\tcreateHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.valuesFiles, \"values\", nil, \"local path to values.yaml files, also accepts comma-separated values\")\n\tcreateHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.valuesFrom, \"values-from\", nil, \"a Kubernetes object reference that contains the values.yaml data key in the format '<kind>/<name>', where kind must be one of: (Secret,ConfigMap)\")\n\tcreateHelmReleaseCmd.Flags().Var(&helmReleaseArgs.crds, \"crds\", helmReleaseArgs.crds.Description())\n\tcreateHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.kubeConfigSecretRef, \"kubeconfig-secret-ref\", \"\", \"the name of the Kubernetes Secret that contains a key with the kubeconfig file for connecting to a remote cluster\")\n\tcreateHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.chartRef, \"chart-ref\", \"\", \"the name of the HelmChart resource to use as source for the HelmRelease, in the format '<kind>/<name>.<namespace>', where kind must be one of: (OCIRepository,HelmChart)\")\n\tcreateCmd.AddCommand(createHelmReleaseCmd)\n}\n\nfunc createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tif helmReleaseArgs.storageNamespace == \"\" && helmReleaseArgs.targetNamespace != \"\" {\n\t\thelmReleaseArgs.storageNamespace = helmReleaseArgs.targetNamespace\n\t}\n\n\tif helmReleaseArgs.chart == \"\" && helmReleaseArgs.chartRef == \"\" {\n\t\treturn fmt.Errorf(\"chart or chart-ref is required\")\n\t}\n\n\tif helmReleaseArgs.chart != \"\" && helmReleaseArgs.chartRef != \"\" {\n\t\treturn fmt.Errorf(\"cannot use --chart in combination with --chart-ref\")\n\t}\n\n\tsourceLabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !createArgs.export {\n\t\tlogger.Generatef(\"generating HelmRelease\")\n\t}\n\n\tif !validateStrategy(helmReleaseArgs.reconcileStrategy) {\n\t\treturn fmt.Errorf(\"'%s' is an invalid reconcile strategy(valid: Revision, ChartVersion)\",\n\t\t\thelmReleaseArgs.reconcileStrategy)\n\t}\n\n\thelmRelease := helmv2.HelmRelease{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    sourceLabels,\n\t\t},\n\t\tSpec: helmv2.HelmReleaseSpec{\n\t\t\tReleaseName: helmReleaseArgs.name,\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: createArgs.interval,\n\t\t\t},\n\t\t\tTargetNamespace:  helmReleaseArgs.targetNamespace,\n\t\t\tStorageNamespace: helmReleaseArgs.storageNamespace,\n\t\t\tSuspend:          false,\n\t\t},\n\t}\n\n\tif len(helmReleaseArgs.dependsOn) > 0 {\n\t\tls := utils.MakeDependsOn(helmReleaseArgs.dependsOn)\n\t\thrDependsOn := make([]helmv2.DependencyReference, 0, len(ls))\n\t\tfor _, d := range ls {\n\t\t\thrDependsOn = append(hrDependsOn, helmv2.DependencyReference{\n\t\t\t\tName:      d.Name,\n\t\t\t\tNamespace: d.Namespace,\n\t\t\t})\n\t\t}\n\t\thelmRelease.Spec.DependsOn = hrDependsOn\n\t}\n\n\tswitch {\n\tcase helmReleaseArgs.chart != \"\":\n\t\thelmRelease.Spec.Chart = &helmv2.HelmChartTemplate{\n\t\t\tSpec: helmv2.HelmChartTemplateSpec{\n\t\t\t\tChart:   helmReleaseArgs.chart,\n\t\t\t\tVersion: helmReleaseArgs.chartVersion,\n\t\t\t\tSourceRef: helmv2.CrossNamespaceObjectReference{\n\t\t\t\t\tKind:      helmReleaseArgs.source.Kind,\n\t\t\t\t\tName:      helmReleaseArgs.source.Name,\n\t\t\t\t\tNamespace: helmReleaseArgs.source.Namespace,\n\t\t\t\t},\n\t\t\t\tReconcileStrategy: helmReleaseArgs.reconcileStrategy,\n\t\t\t},\n\t\t}\n\t\tif helmReleaseArgs.chartInterval != 0 {\n\t\t\thelmRelease.Spec.Chart.Spec.Interval = &metav1.Duration{\n\t\t\t\tDuration: helmReleaseArgs.chartInterval,\n\t\t\t}\n\t\t}\n\tcase helmReleaseArgs.chartRef != \"\":\n\t\tkind, name, ns := utils.ParseObjectKindNameNamespace(helmReleaseArgs.chartRef)\n\t\tif kind != sourcev1.HelmChartKind && kind != sourcev1.OCIRepositoryKind {\n\t\t\treturn fmt.Errorf(\"chart reference kind '%s' is not supported, must be one of: %s\",\n\t\t\t\tkind, strings.Join(supportedHelmReleaseReferenceKinds, \", \"))\n\t\t}\n\t\thelmRelease.Spec.ChartRef = &helmv2.CrossNamespaceSourceReference{\n\t\t\tKind:      kind,\n\t\t\tName:      name,\n\t\t\tNamespace: ns,\n\t\t}\n\t}\n\n\tif helmReleaseArgs.kubeConfigSecretRef != \"\" {\n\t\thelmRelease.Spec.KubeConfig = &meta.KubeConfigReference{\n\t\t\tSecretRef: &meta.SecretKeyReference{\n\t\t\t\tName: helmReleaseArgs.kubeConfigSecretRef,\n\t\t\t},\n\t\t}\n\t}\n\n\tif helmReleaseArgs.createNamespace {\n\t\tif helmRelease.Spec.Install == nil {\n\t\t\thelmRelease.Spec.Install = &helmv2.Install{}\n\t\t}\n\n\t\thelmRelease.Spec.Install.CreateNamespace = helmReleaseArgs.createNamespace\n\t}\n\n\tif helmReleaseArgs.saName != \"\" {\n\t\thelmRelease.Spec.ServiceAccountName = helmReleaseArgs.saName\n\t}\n\n\tif helmReleaseArgs.crds != \"\" {\n\t\tif helmRelease.Spec.Install == nil {\n\t\t\thelmRelease.Spec.Install = &helmv2.Install{}\n\t\t}\n\n\t\thelmRelease.Spec.Install.CRDs = helmv2.Create\n\t\thelmRelease.Spec.Upgrade = &helmv2.Upgrade{CRDs: helmv2.CRDsPolicy(helmReleaseArgs.crds.String())}\n\t}\n\n\tif len(helmReleaseArgs.valuesFiles) > 0 {\n\t\tvaluesMap := make(map[string]interface{})\n\t\tfor _, v := range helmReleaseArgs.valuesFiles {\n\t\t\tdata, err := os.ReadFile(v)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"reading values from %s failed: %w\", v, err)\n\t\t\t}\n\n\t\t\tjsonBytes, err := yaml.YAMLToJSON(data)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"converting values to JSON from %s failed: %w\", v, err)\n\t\t\t}\n\n\t\t\tjsonMap := make(map[string]interface{})\n\t\t\tif err := json.Unmarshal(jsonBytes, &jsonMap); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unmarshaling values from %s failed: %w\", v, err)\n\t\t\t}\n\n\t\t\tvaluesMap = transform.MergeMaps(valuesMap, jsonMap)\n\t\t}\n\n\t\tjsonRaw, err := json.Marshal(valuesMap)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"marshaling values failed: %w\", err)\n\t\t}\n\n\t\thelmRelease.Spec.Values = &apiextensionsv1.JSON{Raw: jsonRaw}\n\t}\n\n\tif len(helmReleaseArgs.valuesFrom) != 0 {\n\t\tvalues := []helmv2.ValuesReference{}\n\t\tfor _, value := range helmReleaseArgs.valuesFrom {\n\t\t\tsourceKind, sourceName := utils.ParseObjectKindName(value)\n\t\t\tif sourceKind == \"\" {\n\t\t\t\treturn fmt.Errorf(\"invalid Kubernetes object reference '%s', must be in format <kind>/<name>\", value)\n\t\t\t}\n\t\t\tcleanSourceKind, ok := utils.ContainsEqualFoldItemString(supportedHelmReleaseValuesFromKinds, sourceKind)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"reference kind '%s' is not supported, must be one of: %s\",\n\t\t\t\t\tsourceKind, strings.Join(supportedHelmReleaseValuesFromKinds, \", \"))\n\t\t\t}\n\n\t\t\tvalues = append(values, helmv2.ValuesReference{\n\t\t\t\tName: sourceName,\n\t\t\t\tKind: cleanSourceKind,\n\t\t\t})\n\t\t}\n\t\thelmRelease.Spec.ValuesFrom = values\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportHelmRelease(&helmRelease))\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"applying HelmRelease\")\n\tnamespacedName, err := upsertHelmRelease(ctx, kubeClient, &helmRelease)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Waitingf(\"waiting for HelmRelease reconciliation\")\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,\n\t\tisObjectReadyConditionFunc(kubeClient, namespacedName, &helmRelease)); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"HelmRelease %s is ready\", name)\n\n\tlogger.Successf(\"applied revision %s\", getHelmReleaseRevision(helmRelease))\n\treturn nil\n}\n\nfunc upsertHelmRelease(ctx context.Context, kubeClient client.Client,\n\thelmRelease *helmv2.HelmRelease) (types.NamespacedName, error) {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: helmRelease.GetNamespace(),\n\t\tName:      helmRelease.GetName(),\n\t}\n\n\tvar existing helmv2.HelmRelease\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, helmRelease); err != nil {\n\t\t\t\treturn namespacedName, err\n\t\t\t} else {\n\t\t\t\tlogger.Successf(\"HelmRelease created\")\n\t\t\t\treturn namespacedName, nil\n\t\t\t}\n\t\t}\n\t\treturn namespacedName, err\n\t}\n\n\texisting.Labels = helmRelease.Labels\n\texisting.Spec = helmRelease.Spec\n\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\treturn namespacedName, err\n\t}\n\thelmRelease = &existing\n\tlogger.Successf(\"HelmRelease updated\")\n\treturn namespacedName, nil\n}\n\nfunc validateStrategy(input string) bool {\n\tallowedStrategy := []string{\"Revision\", \"ChartVersion\"}\n\n\tfor _, strategy := range allowedStrategy {\n\t\tif strategy == input {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/create_helmrelease_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport \"testing\"\n\nfunc TestCreateHelmRelease(t *testing.T) {\n\ttmpl := map[string]string{\n\t\t\"fluxns\": allocateNamespace(\"flux-system\"),\n\t}\n\tsetupHRSource(t, tmpl)\n\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\tname:   \"missing name\",\n\t\t\targs:   \"create helmrelease --export\",\n\t\t\tassert: assertError(\"name is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"missing chart template and chartRef\",\n\t\t\targs:   \"create helmrelease podinfo --export\",\n\t\t\tassert: assertError(\"chart or chart-ref is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"chart and chartRef used in combination\",\n\t\t\targs:   \"create helmrelease podinfo --chart podinfo --chart-ref foobar/podinfo --export\",\n\t\t\tassert: assertError(\"cannot use --chart in combination with --chart-ref\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"unknown source kind\",\n\t\t\targs:   \"create helmrelease podinfo --source foobar/podinfo --chart podinfo --export\",\n\t\t\tassert: assertError(`invalid argument \"foobar/podinfo\" for \"--source\" flag: source kind 'foobar' is not supported, must be one of: HelmRepository, GitRepository, Bucket`),\n\t\t},\n\t\t{\n\t\t\tname:   \"unknown chart reference kind\",\n\t\t\targs:   \"create helmrelease podinfo --chart-ref foobar/podinfo --export\",\n\t\t\tassert: assertError(`chart reference kind 'foobar' is not supported, must be one of: OCIRepository, HelmChart`),\n\t\t},\n\t\t{\n\t\t\tname:   \"basic helmrelease\",\n\t\t\targs:   \"create helmrelease podinfo --source Helmrepository/podinfo --chart podinfo --interval=1m0s --export\",\n\t\t\tassert: assertGoldenTemplateFile(\"testdata/create_hr/basic.yaml\", tmpl),\n\t\t},\n\t\t{\n\t\t\tname:   \"chart with OCIRepository source\",\n\t\t\targs:   \"create helmrelease podinfo --chart-ref OCIRepository/podinfo --interval=1m0s --export\",\n\t\t\tassert: assertGoldenTemplateFile(\"testdata/create_hr/or_basic.yaml\", tmpl),\n\t\t},\n\t\t{\n\t\t\tname:   \"chart with HelmChart source\",\n\t\t\targs:   \"create helmrelease podinfo --chart-ref HelmChart/podinfo --interval=1m0s --export\",\n\t\t\tassert: assertGoldenTemplateFile(\"testdata/create_hr/hc_basic.yaml\", tmpl),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args + \" -n \" + tmpl[\"fluxns\"],\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n\nfunc setupHRSource(t *testing.T, tmpl map[string]string) {\n\tt.Helper()\n\ttestEnv.CreateObjectFile(\"./testdata/create_hr/setup-source.yaml\", tmpl, t)\n}\n"
  },
  {
    "path": "cmd/flux/create_image.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar createImageCmd = &cobra.Command{\n\tUse:   \"image\",\n\tShort: \"Create or update resources dealing with image automation\",\n\tLong: `The create image sub-commands work with image automation objects;\nthat is, object controlling updates to git based on e.g., new container images\nbeing available.`,\n}\n\nfunc init() {\n\tcreateCmd.AddCommand(createImageCmd)\n}\n"
  },
  {
    "path": "cmd/flux/create_image_policy.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"regexp/syntax\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar createImagePolicyCmd = &cobra.Command{\n\tUse:   \"policy [name]\",\n\tShort: \"Create or update an ImagePolicy object\",\n\tLong: `The create image policy command generates an ImagePolicy resource.\nAn ImagePolicy object calculates a \"latest image\" given an image\nrepository and a policy, e.g., semver.\n\nThe image that sorts highest according to the policy is recorded in\nthe status of the object.`,\n\tExample: `  # Create an ImagePolicy to select the latest stable release\n  flux create image policy podinfo \\\n    --image-ref=podinfo \\\n    --select-semver=\">=1.0.0\"\n\n  # Create an ImagePolicy to select the latest main branch build tagged as \"${GIT_BRANCH}-${GIT_SHA:0:7}-$(date +%s)\"\n  flux create image policy podinfo \\\n    --image-ref=podinfo \\\n    --select-numeric=asc \\\n\t--filter-regex='^main-[a-f0-9]+-(?P<ts>[0-9]+)' \\\n\t--filter-extract='$ts'`,\n\tRunE: createImagePolicyRun}\n\ntype imagePolicyFlags struct {\n\timageRef      string\n\tsemver        string\n\talpha         string\n\tnumeric       string\n\tfilterRegex   string\n\tfilterExtract string\n\treflectDigest string\n\tinterval      time.Duration\n}\n\nvar imagePolicyArgs = imagePolicyFlags{}\n\nfunc init() {\n\tflags := createImagePolicyCmd.Flags()\n\tflags.StringVar(&imagePolicyArgs.imageRef, \"image-ref\", \"\", \"the name of an image repository object\")\n\tflags.StringVar(&imagePolicyArgs.semver, \"select-semver\", \"\", \"a semver range to apply to tags; e.g., '1.x'\")\n\tflags.StringVar(&imagePolicyArgs.alpha, \"select-alpha\", \"\", \"use alphabetical sorting to select image; either \\\"asc\\\" meaning select the last, or \\\"desc\\\" meaning select the first\")\n\tflags.StringVar(&imagePolicyArgs.numeric, \"select-numeric\", \"\", \"use numeric sorting to select image; either \\\"asc\\\" meaning select the last, or \\\"desc\\\" meaning select the first\")\n\tflags.StringVar(&imagePolicyArgs.filterRegex, \"filter-regex\", \"\", \"regular expression pattern used to filter the image tags\")\n\tflags.StringVar(&imagePolicyArgs.filterExtract, \"filter-extract\", \"\", \"replacement pattern (using capture groups from --filter-regex) to use for sorting\")\n\tflags.StringVar(&imagePolicyArgs.reflectDigest, \"reflect-digest\", \"\", \"the digest reflection policy to use when observing latest image tags (one of 'Never', 'IfNotPresent', 'Never')\")\n\tflags.DurationVar(&imagePolicyArgs.interval, \"interval\", 0, \"the interval at which to check for new image digests when the policy is set to 'Always'\")\n\n\tcreateImageCmd.AddCommand(createImagePolicyCmd)\n}\n\nfunc createImagePolicyRun(cmd *cobra.Command, args []string) error {\n\tobjectName := args[0]\n\n\tif imagePolicyArgs.imageRef == \"\" {\n\t\treturn fmt.Errorf(\"the name of an ImageRepository in the namespace is required (--image-ref)\")\n\t}\n\n\tlabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar policy = imagev1.ImagePolicy{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      objectName,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    labels,\n\t\t},\n\t\tSpec: imagev1.ImagePolicySpec{\n\t\t\tImageRepositoryRef: meta.NamespacedObjectReference{\n\t\t\t\tName: imagePolicyArgs.imageRef,\n\t\t\t},\n\t\t},\n\t}\n\n\tswitch {\n\tcase imagePolicyArgs.semver != \"\" && imagePolicyArgs.alpha != \"\":\n\tcase imagePolicyArgs.semver != \"\" && imagePolicyArgs.numeric != \"\":\n\tcase imagePolicyArgs.alpha != \"\" && imagePolicyArgs.numeric != \"\":\n\t\treturn fmt.Errorf(\"only one of --select-semver, --select-alpha or --select-numeric can be specified\")\n\tcase imagePolicyArgs.semver != \"\":\n\t\tpolicy.Spec.Policy.SemVer = &imagev1.SemVerPolicy{\n\t\t\tRange: imagePolicyArgs.semver,\n\t\t}\n\tcase imagePolicyArgs.alpha != \"\":\n\t\tif imagePolicyArgs.alpha != \"desc\" && imagePolicyArgs.alpha != \"asc\" {\n\t\t\treturn fmt.Errorf(\"--select-alpha must be one of [\\\"asc\\\", \\\"desc\\\"]\")\n\t\t}\n\t\tpolicy.Spec.Policy.Alphabetical = &imagev1.AlphabeticalPolicy{\n\t\t\tOrder: imagePolicyArgs.alpha,\n\t\t}\n\tcase imagePolicyArgs.numeric != \"\":\n\t\tif imagePolicyArgs.numeric != \"desc\" && imagePolicyArgs.numeric != \"asc\" {\n\t\t\treturn fmt.Errorf(\"--select-numeric must be one of [\\\"asc\\\", \\\"desc\\\"]\")\n\t\t}\n\t\tpolicy.Spec.Policy.Numerical = &imagev1.NumericalPolicy{\n\t\t\tOrder: imagePolicyArgs.numeric,\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"a policy must be provided with either --select-semver or --select-alpha\")\n\t}\n\n\tif imagePolicyArgs.filterRegex != \"\" {\n\t\texp, err := syntax.Parse(imagePolicyArgs.filterRegex, syntax.Perl)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"--filter-regex is an invalid regex pattern\")\n\t\t}\n\t\tpolicy.Spec.FilterTags = &imagev1.TagFilter{\n\t\t\tPattern: imagePolicyArgs.filterRegex,\n\t\t}\n\n\t\tif imagePolicyArgs.filterExtract != \"\" {\n\t\t\tif err := validateExtractStr(imagePolicyArgs.filterExtract, exp.CapNames()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tpolicy.Spec.FilterTags.Extract = imagePolicyArgs.filterExtract\n\t\t}\n\t} else if imagePolicyArgs.filterExtract != \"\" {\n\t\treturn fmt.Errorf(\"cannot specify --filter-extract without specifying --filter-regex\")\n\t}\n\n\tif p := imagev1.ReflectionPolicy(imagePolicyArgs.reflectDigest); p != \"\" {\n\t\tif p != imagev1.ReflectNever && p != imagev1.ReflectIfNotPresent && p != imagev1.ReflectAlways {\n\t\t\treturn fmt.Errorf(\"invalid value for --reflect-digest, must be one of 'Never', 'IfNotPresent', 'Always'\")\n\t\t}\n\t\tpolicy.Spec.DigestReflectionPolicy = p\n\t}\n\n\tif imagePolicyArgs.interval != 0 {\n\t\tif imagePolicyArgs.reflectDigest != string(imagev1.ReflectAlways) {\n\t\t\treturn fmt.Errorf(\"the --interval flag can only be used with the 'Always' digest reflection policy, use --reflect-digest=Always\")\n\t\t}\n\t\tpolicy.Spec.Interval = &metav1.Duration{Duration: imagePolicyArgs.interval}\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportImagePolicy(&policy))\n\t}\n\n\tvar existing imagev1.ImagePolicy\n\tcopyName(&existing, &policy)\n\terr = imagePolicyType.upsertAndWait(imagePolicyAdapter{&existing}, func() error {\n\t\texisting.Spec = policy.Spec\n\t\texisting.SetLabels(policy.Labels)\n\t\treturn nil\n\t})\n\treturn err\n}\n\n// Performs a dry-run of the extract function in Regexp to validate the template\nfunc validateExtractStr(template string, capNames []string) error {\n\tfor len(template) > 0 {\n\t\ti := strings.Index(template, \"$\")\n\t\tif i < 0 {\n\t\t\treturn nil\n\t\t}\n\t\ttemplate = template[i:]\n\t\tif len(template) > 1 && template[1] == '$' {\n\t\t\ttemplate = template[2:]\n\t\t\tcontinue\n\t\t}\n\t\tname, num, rest, ok := extract(template)\n\t\tif !ok {\n\t\t\t// Malformed extract string, assume user didn't want this\n\t\t\treturn fmt.Errorf(\"--filter-extract is malformed\")\n\t\t}\n\t\ttemplate = rest\n\t\tif num >= 0 {\n\t\t\t// we won't worry about numbers as we can't validate these\n\t\t\tcontinue\n\t\t} else {\n\t\t\tfound := false\n\t\t\tfor _, capName := range capNames {\n\t\t\t\tif name == capName {\n\t\t\t\t\tfound = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\treturn fmt.Errorf(\"capture group $%s used in --filter-extract not found in --filter-regex\", name)\n\t\t\t}\n\t\t}\n\n\t}\n\treturn nil\n}\n\n// extract method from the regexp package\n// returns the name or number of the value prepended by $\nfunc extract(str string) (name string, num int, rest string, ok bool) {\n\tif len(str) < 2 || str[0] != '$' {\n\t\treturn\n\t}\n\tbrace := false\n\tif str[1] == '{' {\n\t\tbrace = true\n\t\tstr = str[2:]\n\t} else {\n\t\tstr = str[1:]\n\t}\n\ti := 0\n\tfor i < len(str) {\n\t\trune, size := utf8.DecodeRuneInString(str[i:])\n\t\tif !unicode.IsLetter(rune) && !unicode.IsDigit(rune) && rune != '_' {\n\t\t\tbreak\n\t\t}\n\t\ti += size\n\t}\n\tif i == 0 {\n\t\t// empty name is not okay\n\t\treturn\n\t}\n\tname = str[:i]\n\tif brace {\n\t\tif i >= len(str) || str[i] != '}' {\n\t\t\t// missing closing brace\n\t\t\treturn\n\t\t}\n\t\ti++\n\t}\n\n\t// Parse number.\n\tnum = 0\n\tfor i := 0; i < len(name); i++ {\n\t\tif name[i] < '0' || '9' < name[i] || num >= 1e8 {\n\t\t\tnum = -1\n\t\t\tbreak\n\t\t}\n\t\tnum = num*10 + int(name[i]) - '0'\n\t}\n\t// Disallow leading zeros.\n\tif name[0] == '0' && len(name) > 1 {\n\t\tnum = -1\n\t}\n\n\trest = str[i:]\n\tok = true\n\treturn\n}\n"
  },
  {
    "path": "cmd/flux/create_image_repository.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/google/go-containerregistry/pkg/name\"\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar createImageRepositoryCmd = &cobra.Command{\n\tUse:   \"repository [name]\",\n\tShort: \"Create or update an ImageRepository object\",\n\tLong: `The create image repository command generates an ImageRepository resource.\nAn ImageRepository object specifies an image repository to scan.`,\n\tExample: `  # Create an ImageRepository object to scan the alpine image repository:\n  flux create image repository alpine-repo --image alpine --interval 20m\n\n  # Create an image repository that uses an image pull secret (assumed to\n  # have been created already):\n  flux create image repository myapp-repo \\\n    --secret-ref image-pull \\\n    --image ghcr.io/example.com/myapp --interval 5m\n\n  # Create a TLS secret for a local image registry using a self-signed\n  # host certificate, and use it to scan an image. ca.pem is a file\n  # containing the CA certificate used to sign the host certificate.\n  flux create secret tls local-registry-cert --ca-file ./ca.pem\n  flux create image repository app-repo \\\n    --cert-secret-ref local-registry-cert \\\n    --image local-registry:5000/app --interval 5m\n\n  # Create a TLS secret with a client certificate and key, and use it\n  # to scan a private image registry.\n  flux create secret tls client-cert \\\n    --cert-file client.crt --key-file client.key\n  flux create image repository app-repo \\\n    --cert-secret-ref client-cert \\\n    --image registry.example.com/private/app --interval 5m`,\n\tRunE: createImageRepositoryRun,\n}\n\ntype imageRepoFlags struct {\n\timage         string\n\tsecretRef     string\n\tcertSecretRef string\n\ttimeout       time.Duration\n}\n\nvar imageRepoArgs = imageRepoFlags{}\n\nfunc init() {\n\tflags := createImageRepositoryCmd.Flags()\n\tflags.StringVar(&imageRepoArgs.image, \"image\", \"\", \"the image repository to scan; e.g., library/alpine\")\n\tflags.StringVar(&imageRepoArgs.secretRef, \"secret-ref\", \"\", \"the name of a docker-registry secret to use for credentials\")\n\tflags.StringVar(&imageRepoArgs.certSecretRef, \"cert-ref\", \"\", \"the name of a secret to use for TLS certificates\")\n\t// NB there is already a --timeout in the global flags, for\n\t// controlling timeout on operations while e.g., creating objects.\n\tflags.DurationVar(&imageRepoArgs.timeout, \"scan-timeout\", 0, \"a timeout for scanning; this defaults to the interval if not set\")\n\n\tcreateImageCmd.AddCommand(createImageRepositoryCmd)\n}\n\nfunc createImageRepositoryRun(cmd *cobra.Command, args []string) error {\n\tobjectName := args[0]\n\n\tif imageRepoArgs.image == \"\" {\n\t\treturn fmt.Errorf(\"an image repository (--image) is required\")\n\t}\n\n\tif _, err := name.NewRepository(imageRepoArgs.image); err != nil {\n\t\treturn fmt.Errorf(\"unable to parse image value: %w\", err)\n\t}\n\n\tlabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar repo = imagev1.ImageRepository{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      objectName,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    labels,\n\t\t},\n\t\tSpec: imagev1.ImageRepositorySpec{\n\t\t\tImage:    imageRepoArgs.image,\n\t\t\tInterval: metav1.Duration{Duration: createArgs.interval},\n\t\t},\n\t}\n\tif imageRepoArgs.timeout != 0 {\n\t\trepo.Spec.Timeout = &metav1.Duration{Duration: imageRepoArgs.timeout}\n\t}\n\tif imageRepoArgs.secretRef != \"\" {\n\t\trepo.Spec.SecretRef = &meta.LocalObjectReference{\n\t\t\tName: imageRepoArgs.secretRef,\n\t\t}\n\t}\n\tif imageRepoArgs.certSecretRef != \"\" {\n\t\trepo.Spec.CertSecretRef = &meta.LocalObjectReference{\n\t\t\tName: imageRepoArgs.certSecretRef,\n\t\t}\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportImageRepository(&repo))\n\t}\n\n\t// a temp value for use with the rest\n\tvar existing imagev1.ImageRepository\n\tcopyName(&existing, &repo)\n\terr = imageRepositoryType.upsertAndWait(imageRepositoryAdapter{&existing}, func() error {\n\t\texisting.Spec = repo.Spec\n\t\texisting.Labels = repo.Labels\n\t\treturn nil\n\t})\n\treturn err\n}\n"
  },
  {
    "path": "cmd/flux/create_image_update.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar createImageUpdateCmd = &cobra.Command{\n\tUse:   \"update [name]\",\n\tShort: \"Create or update an ImageUpdateAutomation object\",\n\tLong: `The create image update command generates an ImageUpdateAutomation resource.\nAn ImageUpdateAutomation object specifies an automated update to images\nmentioned in YAMLs in a git repository.`,\n\tExample: `  # Configure image updates for the main repository created by flux bootstrap\n  flux create image update flux-system \\\n    --git-repo-ref=flux-system \\\n    --git-repo-path=\"./clusters/my-cluster\" \\\n    --checkout-branch=main \\\n    --author-name=flux \\\n    --author-email=flux@example.com \\\n    --commit-template=\"{{range .Updated.Images}}{{println .}}{{end}}\"\n\n  # Configure image updates to push changes to a different branch, if the branch doesn't exists it will be created\n  flux create image update flux-system \\\n    --git-repo-ref=flux-system \\\n    --git-repo-path=\"./clusters/my-cluster\" \\\n    --checkout-branch=main \\\n    --push-branch=image-updates \\\n    --author-name=flux \\\n    --author-email=flux@example.com \\\n    --commit-template=\"{{range .Updated.Images}}{{println .}}{{end}}\"\n\n  # Configure image updates for a Git repository in a different namespace\n  flux create image update apps \\\n    --namespace=apps \\\n    --git-repo-ref=flux-system \\\n    --git-repo-namespace=flux-system \\\n    --git-repo-path=\"./clusters/my-cluster\" \\\n    --checkout-branch=main \\\n    --push-branch=image-updates \\\n    --author-name=flux \\\n    --author-email=flux@example.com \\\n    --commit-template=\"{{range .Updated.Images}}{{println .}}{{end}}\"\n`,\n\tRunE: createImageUpdateRun,\n}\n\ntype imageUpdateFlags struct {\n\tgitRepoName      string\n\tgitRepoNamespace string\n\tgitRepoPath      string\n\tcheckoutBranch   string\n\tpushBranch       string\n\tcommitTemplate   string\n\tauthorName       string\n\tauthorEmail      string\n}\n\nvar imageUpdateArgs = imageUpdateFlags{}\n\nfunc init() {\n\tflags := createImageUpdateCmd.Flags()\n\tflags.StringVar(&imageUpdateArgs.gitRepoName, \"git-repo-ref\", \"\", \"the name of a GitRepository resource with details of the upstream Git repository\")\n\tflags.StringVar(&imageUpdateArgs.gitRepoNamespace, \"git-repo-namespace\", \"\", \"the namespace of the GitRepository resource, defaults to the ImageUpdateAutomation namespace\")\n\tflags.StringVar(&imageUpdateArgs.gitRepoPath, \"git-repo-path\", \"\", \"path to the directory containing the manifests to be updated, defaults to the repository root\")\n\tflags.StringVar(&imageUpdateArgs.checkoutBranch, \"checkout-branch\", \"\", \"the branch to checkout\")\n\tflags.StringVar(&imageUpdateArgs.pushBranch, \"push-branch\", \"\", \"the branch to push commits to, defaults to the checkout branch if not specified\")\n\tflags.StringVar(&imageUpdateArgs.commitTemplate, \"commit-template\", \"\", \"a template for commit messages\")\n\tflags.StringVar(&imageUpdateArgs.authorName, \"author-name\", \"\", \"the name to use for commit author\")\n\tflags.StringVar(&imageUpdateArgs.authorEmail, \"author-email\", \"\", \"the email to use for commit author\")\n\n\tcreateImageCmd.AddCommand(createImageUpdateCmd)\n}\n\nfunc createImageUpdateRun(cmd *cobra.Command, args []string) error {\n\tobjectName := args[0]\n\n\tif imageUpdateArgs.gitRepoName == \"\" {\n\t\treturn fmt.Errorf(\"a reference to a GitRepository is required (--git-repo-ref)\")\n\t}\n\n\tif imageUpdateArgs.checkoutBranch == \"\" {\n\t\treturn fmt.Errorf(\"the Git repository branch is required (--checkout-branch)\")\n\t}\n\n\tif imageUpdateArgs.authorName == \"\" {\n\t\treturn fmt.Errorf(\"the author name is required (--author-name)\")\n\t}\n\n\tif imageUpdateArgs.authorEmail == \"\" {\n\t\treturn fmt.Errorf(\"the author email is required (--author-email)\")\n\t}\n\n\tlabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar update = autov1.ImageUpdateAutomation{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      objectName,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    labels,\n\t\t},\n\t\tSpec: autov1.ImageUpdateAutomationSpec{\n\t\t\tSourceRef: autov1.CrossNamespaceSourceReference{\n\t\t\t\tKind:      sourcev1.GitRepositoryKind,\n\t\t\t\tName:      imageUpdateArgs.gitRepoName,\n\t\t\t\tNamespace: imageUpdateArgs.gitRepoNamespace,\n\t\t\t},\n\n\t\t\tGitSpec: &autov1.GitSpec{\n\t\t\t\tCheckout: &autov1.GitCheckoutSpec{\n\t\t\t\t\tReference: sourcev1.GitRepositoryRef{\n\t\t\t\t\t\tBranch: imageUpdateArgs.checkoutBranch,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tCommit: autov1.CommitSpec{\n\t\t\t\t\tAuthor: autov1.CommitUser{\n\t\t\t\t\t\tName:  imageUpdateArgs.authorName,\n\t\t\t\t\t\tEmail: imageUpdateArgs.authorEmail,\n\t\t\t\t\t},\n\t\t\t\t\tMessageTemplate: imageUpdateArgs.commitTemplate,\n\t\t\t\t},\n\t\t\t},\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: createArgs.interval,\n\t\t\t},\n\t\t},\n\t}\n\n\tif imageUpdateArgs.pushBranch != \"\" {\n\t\tupdate.Spec.GitSpec.Push = &autov1.PushSpec{\n\t\t\tBranch: imageUpdateArgs.pushBranch,\n\t\t}\n\t}\n\n\tif imageUpdateArgs.gitRepoPath != \"\" {\n\t\tupdate.Spec.Update = &autov1.UpdateStrategy{\n\t\t\tPath:     imageUpdateArgs.gitRepoPath,\n\t\t\tStrategy: autov1.UpdateStrategySetters,\n\t\t}\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportImageUpdate(&update))\n\t}\n\n\tvar existing autov1.ImageUpdateAutomation\n\tcopyName(&existing, &update)\n\terr = imageUpdateAutomationType.upsertAndWait(imageUpdateAutomationAdapter{&existing}, func() error {\n\t\texisting.Spec = update.Spec\n\t\texisting.Labels = update.Labels\n\t\treturn nil\n\t})\n\treturn err\n}\n"
  },
  {
    "path": "cmd/flux/create_kustomization.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar createKsCmd = &cobra.Command{\n\tUse:     \"kustomization [name]\",\n\tAliases: []string{\"ks\"},\n\tShort:   \"Create or update a Kustomization resource\",\n\tLong:    `The create command generates a Kustomization resource for a given source.`,\n\tExample: `  # Create a Kustomization resource from a source at a given path\n  flux create kustomization kyverno \\\n    --source=GitRepository/kyverno \\\n    --path=\"./config/release\" \\\n    --prune=true \\\n    --interval=60m \\\n    --wait=true \\\n    --health-check-timeout=3m\n\n  # Create a Kustomization resource that depends on the previous one\n  flux create kustomization kyverno-policies \\\n    --depends-on=kyverno \\\n    --source=GitRepository/kyverno-policies \\\n    --path=\"./policies/flux\" \\\n    --prune=true \\\n    --interval=5m\n\n  # Create a Kustomization using a source from a different namespace\n  flux create kustomization podinfo \\\n    --namespace=default \\\n    --source=GitRepository/podinfo.flux-system \\\n    --path=\"./kustomize\" \\\n    --prune=true \\\n    --interval=5m\n\n  # Create a Kustomization resource that references an OCIRepository\n  flux create kustomization podinfo \\\n    --source=OCIRepository/podinfo \\\n    --target-namespace=default \\\n    --prune=true \\\n    --interval=5m\n\n  # Create a Kustomization resource that references a Bucket\n  flux create kustomization secrets \\\n    --source=Bucket/secrets \\\n    --prune=true \\\n    --interval=5m`,\n\tRunE: createKsCmdRun,\n}\n\ntype kustomizationFlags struct {\n\tsource              flags.KustomizationSource\n\tpath                flags.SafeRelativePath\n\tprune               bool\n\tdependsOn           []string\n\tvalidation          string\n\thealthCheck         []string\n\thealthTimeout       time.Duration\n\tsaName              string\n\tdecryptionProvider  flags.DecryptionProvider\n\tdecryptionSecret    string\n\ttargetNamespace     string\n\twait                bool\n\tkubeConfigSecretRef string\n\tretryInterval       time.Duration\n}\n\nvar kustomizationArgs = NewKustomizationFlags()\n\nfunc init() {\n\tcreateKsCmd.Flags().Var(&kustomizationArgs.source, \"source\", kustomizationArgs.source.Description())\n\tcreateKsCmd.Flags().Var(&kustomizationArgs.path, \"path\", \"path to the directory containing a kustomization.yaml file\")\n\tcreateKsCmd.Flags().BoolVar(&kustomizationArgs.prune, \"prune\", false, \"enable garbage collection\")\n\tcreateKsCmd.Flags().BoolVar(&kustomizationArgs.wait, \"wait\", false, \"enable health checking of all the applied resources\")\n\tcreateKsCmd.Flags().StringSliceVar(&kustomizationArgs.healthCheck, \"health-check\", nil, \"workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'\")\n\tcreateKsCmd.Flags().DurationVar(&kustomizationArgs.healthTimeout, \"health-check-timeout\", 2*time.Minute, \"timeout of health checking operations\")\n\tcreateKsCmd.Flags().StringVar(&kustomizationArgs.validation, \"validation\", \"\", \"validate the manifests before applying them on the cluster, can be 'client' or 'server'\")\n\tcreateKsCmd.Flags().StringSliceVar(&kustomizationArgs.dependsOn, \"depends-on\", nil, \"Kustomization that must be ready before this Kustomization can be applied, supported formats '<name>' and '<namespace>/<name>', also accepts comma-separated values\")\n\tcreateKsCmd.Flags().StringVar(&kustomizationArgs.saName, \"service-account\", \"\", \"the name of the service account to impersonate when reconciling this Kustomization\")\n\tcreateKsCmd.Flags().Var(&kustomizationArgs.decryptionProvider, \"decryption-provider\", kustomizationArgs.decryptionProvider.Description())\n\tcreateKsCmd.Flags().StringVar(&kustomizationArgs.decryptionSecret, \"decryption-secret\", \"\", \"set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption\")\n\tcreateKsCmd.Flags().StringVar(&kustomizationArgs.targetNamespace, \"target-namespace\", \"\", \"overrides the namespace of all Kustomization objects reconciled by this Kustomization\")\n\tcreateKsCmd.Flags().StringVar(&kustomizationArgs.kubeConfigSecretRef, \"kubeconfig-secret-ref\", \"\", \"the name of the Kubernetes Secret that contains a key with the kubeconfig file for connecting to a remote cluster\")\n\tcreateKsCmd.Flags().MarkDeprecated(\"validation\", \"this arg is no longer used, all resources are validated using server-side apply dry-run\")\n\tcreateKsCmd.Flags().DurationVar(&kustomizationArgs.retryInterval, \"retry-interval\", 0, \"the interval at which to retry a previously failed reconciliation\")\n\n\tcreateCmd.AddCommand(createKsCmd)\n}\n\nfunc NewKustomizationFlags() kustomizationFlags {\n\treturn kustomizationFlags{\n\t\tpath: \"./\",\n\t}\n}\n\nfunc createKsCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tif kustomizationArgs.path == \"\" {\n\t\treturn fmt.Errorf(\"path is required\")\n\t}\n\tif !strings.HasPrefix(kustomizationArgs.path.String(), \"./\") {\n\t\treturn fmt.Errorf(\"path must begin with ./\")\n\t}\n\n\tif !createArgs.export {\n\t\tlogger.Generatef(\"generating Kustomization\")\n\t}\n\n\tkslabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkustomization := kustomizev1.Kustomization{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    kslabels,\n\t\t},\n\t\tSpec: kustomizev1.KustomizationSpec{\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: createArgs.interval,\n\t\t\t},\n\t\t\tPath:  kustomizationArgs.path.ToSlash(),\n\t\t\tPrune: kustomizationArgs.prune,\n\t\t\tSourceRef: kustomizev1.CrossNamespaceSourceReference{\n\t\t\t\tKind:      kustomizationArgs.source.Kind,\n\t\t\t\tName:      kustomizationArgs.source.Name,\n\t\t\t\tNamespace: kustomizationArgs.source.Namespace,\n\t\t\t},\n\t\t\tSuspend:         false,\n\t\t\tTargetNamespace: kustomizationArgs.targetNamespace,\n\t\t},\n\t}\n\n\tif len(kustomizationArgs.dependsOn) > 0 {\n\t\tls := utils.MakeDependsOn(kustomizationArgs.dependsOn)\n\t\tksDependsOn := make([]kustomizev1.DependencyReference, 0, len(ls))\n\t\tfor _, d := range ls {\n\t\t\tksDependsOn = append(ksDependsOn, kustomizev1.DependencyReference{\n\t\t\t\tName:      d.Name,\n\t\t\t\tNamespace: d.Namespace,\n\t\t\t})\n\t\t}\n\t\tkustomization.Spec.DependsOn = ksDependsOn\n\t}\n\n\tif kustomizationArgs.kubeConfigSecretRef != \"\" {\n\t\tkustomization.Spec.KubeConfig = &meta.KubeConfigReference{\n\t\t\tSecretRef: &meta.SecretKeyReference{\n\t\t\t\tName: kustomizationArgs.kubeConfigSecretRef,\n\t\t\t},\n\t\t}\n\t}\n\n\tif len(kustomizationArgs.healthCheck) > 0 && !kustomizationArgs.wait {\n\t\thealthChecks := make([]meta.NamespacedObjectKindReference, 0)\n\t\tfor _, w := range kustomizationArgs.healthCheck {\n\t\t\tkindObj := strings.Split(w, \"/\")\n\t\t\tif len(kindObj) != 2 {\n\t\t\t\treturn fmt.Errorf(\"invalid health check '%s' must be in the format 'kind/name.namespace' %v\", w, kindObj)\n\t\t\t}\n\t\t\tkind := kindObj[0]\n\n\t\t\t//TODO: (stefan) extend this list with all the kstatus builtin kinds\n\t\t\tkinds := map[string]bool{\n\t\t\t\t\"Deployment\":           true,\n\t\t\t\t\"DaemonSet\":            true,\n\t\t\t\t\"StatefulSet\":          true,\n\t\t\t\thelmv2.HelmReleaseKind: true,\n\t\t\t}\n\t\t\tif !kinds[kind] {\n\t\t\t\treturn fmt.Errorf(\"invalid health check kind '%s' can be HelmRelease, Deployment, DaemonSet or StatefulSet\", kind)\n\t\t\t}\n\t\t\tnameNs := strings.Split(kindObj[1], \".\")\n\t\t\tif len(nameNs) != 2 {\n\t\t\t\treturn fmt.Errorf(\"invalid health check '%s' must be in the format 'kind/name.namespace'\", w)\n\t\t\t}\n\n\t\t\tcheck := meta.NamespacedObjectKindReference{\n\t\t\t\tKind:      kind,\n\t\t\t\tName:      nameNs[0],\n\t\t\t\tNamespace: nameNs[1],\n\t\t\t}\n\n\t\t\tif kind == helmv2.HelmReleaseKind {\n\t\t\t\tcheck.APIVersion = helmv2.GroupVersion.String()\n\t\t\t}\n\t\t\thealthChecks = append(healthChecks, check)\n\t\t}\n\t\tkustomization.Spec.HealthChecks = healthChecks\n\t\tkustomization.Spec.Timeout = &metav1.Duration{\n\t\t\tDuration: kustomizationArgs.healthTimeout,\n\t\t}\n\t}\n\n\tif kustomizationArgs.wait {\n\t\tkustomization.Spec.Wait = true\n\t\tkustomization.Spec.Timeout = &metav1.Duration{\n\t\t\tDuration: kustomizationArgs.healthTimeout,\n\t\t}\n\t}\n\n\tif kustomizationArgs.saName != \"\" {\n\t\tkustomization.Spec.ServiceAccountName = kustomizationArgs.saName\n\t}\n\n\tif kustomizationArgs.decryptionProvider != \"\" {\n\t\tkustomization.Spec.Decryption = &kustomizev1.Decryption{\n\t\t\tProvider: kustomizationArgs.decryptionProvider.String(),\n\t\t}\n\n\t\tif kustomizationArgs.decryptionSecret != \"\" {\n\t\t\tkustomization.Spec.Decryption.SecretRef = &meta.LocalObjectReference{Name: kustomizationArgs.decryptionSecret}\n\t\t}\n\t}\n\n\tif kustomizationArgs.retryInterval > 0 {\n\t\tkustomization.Spec.RetryInterval = &metav1.Duration{Duration: kustomizationArgs.retryInterval}\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportKs(&kustomization))\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"applying Kustomization\")\n\tnamespacedName, err := upsertKustomization(ctx, kubeClient, &kustomization)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Waitingf(\"waiting for Kustomization reconciliation\")\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,\n\t\tisObjectReadyConditionFunc(kubeClient, namespacedName, &kustomization)); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"Kustomization %s is ready\", name)\n\n\tlogger.Successf(\"applied revision %s\", kustomization.Status.LastAppliedRevision)\n\treturn nil\n}\n\nfunc upsertKustomization(ctx context.Context, kubeClient client.Client,\n\tkustomization *kustomizev1.Kustomization) (types.NamespacedName, error) {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: kustomization.GetNamespace(),\n\t\tName:      kustomization.GetName(),\n\t}\n\n\tvar existing kustomizev1.Kustomization\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, kustomization); err != nil {\n\t\t\t\treturn namespacedName, err\n\t\t\t} else {\n\t\t\t\tlogger.Successf(\"Kustomization created\")\n\t\t\t\treturn namespacedName, nil\n\t\t\t}\n\t\t}\n\t\treturn namespacedName, err\n\t}\n\n\texisting.Labels = kustomization.Labels\n\texisting.Spec = kustomization.Spec\n\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\treturn namespacedName, err\n\t}\n\tkustomization = &existing\n\tlogger.Successf(\"Kustomization updated\")\n\treturn namespacedName, nil\n}\n"
  },
  {
    "path": "cmd/flux/create_receiver.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar createReceiverCmd = &cobra.Command{\n\tUse:   \"receiver [name]\",\n\tShort: \"Create or update a Receiver resource\",\n\tLong:  `The create receiver command generates a Receiver resource.`,\n\tExample: `  # Create a Receiver\n  flux create receiver github-receiver \\\n\t--type github \\\n\t--event ping \\\n\t--event push \\\n\t--secret-ref webhook-token \\\n\t--resource GitRepository/webapp \\\n\t--resource HelmRepository/webapp`,\n\tRunE: createReceiverCmdRun,\n}\n\ntype receiverFlags struct {\n\treceiverType string\n\tsecretRef    string\n\tevents       []string\n\tresources    []string\n}\n\nvar receiverArgs receiverFlags\n\nfunc init() {\n\tcreateReceiverCmd.Flags().StringVar(&receiverArgs.receiverType, \"type\", \"\", \"\")\n\tcreateReceiverCmd.Flags().StringVar(&receiverArgs.secretRef, \"secret-ref\", \"\", \"\")\n\tcreateReceiverCmd.Flags().StringSliceVar(&receiverArgs.events, \"event\", []string{}, \"also accepts comma-separated values\")\n\tcreateReceiverCmd.Flags().StringSliceVar(&receiverArgs.resources, \"resource\", []string{}, \"also accepts comma-separated values\")\n\tcreateCmd.AddCommand(createReceiverCmd)\n}\n\nfunc createReceiverCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tif receiverArgs.receiverType == \"\" {\n\t\treturn fmt.Errorf(\"Receiver type is required\")\n\t}\n\n\tif receiverArgs.secretRef == \"\" {\n\t\treturn fmt.Errorf(\"secret ref is required\")\n\t}\n\n\tresources := []notificationv1.CrossNamespaceObjectReference{}\n\tfor _, resource := range receiverArgs.resources {\n\t\tkind, name := utils.ParseObjectKindName(resource)\n\t\tif kind == \"\" {\n\t\t\treturn fmt.Errorf(\"invalid event source '%s', must be in format <kind>/<name>\", resource)\n\t\t}\n\n\t\tresources = append(resources, notificationv1.CrossNamespaceObjectReference{\n\t\t\tKind: kind,\n\t\t\tName: name,\n\t\t})\n\t}\n\n\tif len(resources) == 0 {\n\t\treturn fmt.Errorf(\"atleast one resource is required\")\n\t}\n\n\tsourceLabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !createArgs.export {\n\t\tlogger.Generatef(\"generating Receiver\")\n\t}\n\n\treceiver := notificationv1.Receiver{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    sourceLabels,\n\t\t},\n\t\tSpec: notificationv1.ReceiverSpec{\n\t\t\tType:      receiverArgs.receiverType,\n\t\t\tEvents:    receiverArgs.events,\n\t\t\tResources: resources,\n\t\t\tSecretRef: meta.LocalObjectReference{\n\t\t\t\tName: receiverArgs.secretRef,\n\t\t\t},\n\t\t\tSuspend: false,\n\t\t},\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportReceiver(&receiver))\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"applying Receiver\")\n\tnamespacedName, err := upsertReceiver(ctx, kubeClient, &receiver)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Waitingf(\"waiting for Receiver reconciliation\")\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,\n\t\tisObjectReadyConditionFunc(kubeClient, namespacedName, &receiver)); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"Receiver %s is ready\", name)\n\n\tlogger.Successf(\"generated webhook URL %s\", receiver.Status.WebhookPath)\n\treturn nil\n}\n\nfunc upsertReceiver(ctx context.Context, kubeClient client.Client,\n\treceiver *notificationv1.Receiver) (types.NamespacedName, error) {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: receiver.GetNamespace(),\n\t\tName:      receiver.GetName(),\n\t}\n\n\tvar existing notificationv1.Receiver\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, receiver); err != nil {\n\t\t\t\treturn namespacedName, err\n\t\t\t} else {\n\t\t\t\tlogger.Successf(\"Receiver created\")\n\t\t\t\treturn namespacedName, nil\n\t\t\t}\n\t\t}\n\t\treturn namespacedName, err\n\t}\n\n\texisting.Labels = receiver.Labels\n\texisting.Spec = receiver.Spec\n\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\treturn namespacedName, err\n\t}\n\treceiver = &existing\n\tlogger.Successf(\"Receiver updated\")\n\treturn namespacedName, nil\n}\n"
  },
  {
    "path": "cmd/flux/create_secret.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n)\n\nvar createSecretCmd = &cobra.Command{\n\tUse:   \"secret\",\n\tShort: \"Create or update Kubernetes secrets\",\n\tLong:  `The create source sub-commands generate Kubernetes secrets specific to Flux.`,\n}\n\nfunc init() {\n\tcreateCmd.AddCommand(createSecretCmd)\n}\n\nfunc upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.Secret) error {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: secret.GetNamespace(),\n\t\tName:      secret.GetName(),\n\t}\n\n\tvar existing corev1.Secret\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, &secret); err != nil {\n\t\t\t\treturn err\n\t\t\t} else {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn err\n\t}\n\n\texisting.StringData = secret.StringData\n\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_git.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"crypto/elliptic\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n)\n\nvar createSecretGitCmd = &cobra.Command{\n\tUse:   \"git [name]\",\n\tShort: \"Create or update a Kubernetes secret for Git authentication\",\n\tLong: `The create secret git command generates a Kubernetes secret with Git credentials.\nFor Git over SSH, the host and SSH keys are automatically generated and stored\nin the secret.\nFor Git over HTTP/S, the provided basic authentication credentials or bearer\nauthentication token are stored in the secret.`,\n\tExample: `  # Create a Git SSH authentication secret using an ECDSA P-521 curve public key\n\n  flux create secret git podinfo-auth \\\n    --url=ssh://git@github.com/stefanprodan/podinfo \\\n    --ssh-key-algorithm=ecdsa \\\n    --ssh-ecdsa-curve=p521\n\n  # Create a Git SSH authentication secret with a passwordless private key from file\n  # The public SSH host key will still be gathered from the host\n  flux create secret git podinfo-auth \\\n    --url=ssh://git@github.com/stefanprodan/podinfo \\\n    --private-key-file=./private.key\n\n  # Create a Git SSH authentication secret with a passworded private key from file\n  # The public SSH host key will still be gathered from the host\n  flux create secret git podinfo-auth \\\n    --url=ssh://git@github.com/stefanprodan/podinfo \\\n    --private-key-file=./private.key \\\n    --password=<password>\n\n  # Create a secret for a Git repository using basic authentication\n  flux create secret git podinfo-auth \\\n    --url=https://github.com/stefanprodan/podinfo \\\n    --username=username \\\n    --password=password\n\n  # Create a Git SSH secret on disk\n  flux create secret git podinfo-auth \\\n    --url=ssh://git@github.com/stefanprodan/podinfo \\\n    --export > podinfo-auth.yaml\n\n  # Print the deploy key\n  yq eval '.stringData.\"identity.pub\"' podinfo-auth.yaml\n\n  # Encrypt the secret on disk with Mozilla SOPS\n  sops --encrypt --encrypted-regex '^(data|stringData)$' \\\n    --in-place podinfo-auth.yaml`,\n\tRunE: createSecretGitCmdRun,\n}\n\ntype secretGitFlags struct {\n\turl            string\n\tusername       string\n\tpassword       string\n\tkeyAlgorithm   flags.PublicKeyAlgorithm\n\trsaBits        flags.RSAKeyBits\n\tecdsaCurve     flags.ECDSACurve\n\tcaCrtFile      string\n\tprivateKeyFile string\n\tbearerToken    string\n}\n\nvar secretGitArgs = NewSecretGitFlags()\n\nfunc init() {\n\tcreateSecretGitCmd.Flags().StringVar(&secretGitArgs.url, \"url\", \"\", \"git address, e.g. ssh://git@host/org/repository\")\n\tcreateSecretGitCmd.Flags().StringVarP(&secretGitArgs.username, \"username\", \"u\", \"\", \"basic authentication username\")\n\tcreateSecretGitCmd.Flags().StringVarP(&secretGitArgs.password, \"password\", \"p\", \"\", \"basic authentication password\")\n\tcreateSecretGitCmd.Flags().Var(&secretGitArgs.keyAlgorithm, \"ssh-key-algorithm\", secretGitArgs.keyAlgorithm.Description())\n\tcreateSecretGitCmd.Flags().Var(&secretGitArgs.rsaBits, \"ssh-rsa-bits\", secretGitArgs.rsaBits.Description())\n\tcreateSecretGitCmd.Flags().Var(&secretGitArgs.ecdsaCurve, \"ssh-ecdsa-curve\", secretGitArgs.ecdsaCurve.Description())\n\tcreateSecretGitCmd.Flags().StringVar(&secretGitArgs.caCrtFile, \"ca-crt-file\", \"\", \"path to TLS CA certificate file used for validating self-signed certificates\")\n\tcreateSecretGitCmd.Flags().StringVar(&secretGitArgs.privateKeyFile, \"private-key-file\", \"\", \"path to a passwordless private key file used for authenticating to the Git SSH server\")\n\tcreateSecretGitCmd.Flags().StringVar(&secretGitArgs.bearerToken, \"bearer-token\", \"\", \"bearer authentication token\")\n\n\tcreateSecretCmd.AddCommand(createSecretGitCmd)\n}\n\nfunc NewSecretGitFlags() secretGitFlags {\n\treturn secretGitFlags{\n\t\tkeyAlgorithm: flags.PublicKeyAlgorithm(sourcesecret.ECDSAPrivateKeyAlgorithm),\n\t\trsaBits:      2048,\n\t\tecdsaCurve:   flags.ECDSACurve{Curve: elliptic.P384()},\n\t}\n}\n\nfunc createSecretGitCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\tif secretGitArgs.url == \"\" {\n\t\treturn fmt.Errorf(\"url is required\")\n\t}\n\n\tu, err := url.Parse(secretGitArgs.url)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"git URL parse failed: %w\", err)\n\t}\n\n\tlabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\topts := sourcesecret.Options{\n\t\tName:         name,\n\t\tNamespace:    *kubeconfigArgs.Namespace,\n\t\tLabels:       labels,\n\t\tManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,\n\t}\n\tswitch u.Scheme {\n\tcase \"ssh\":\n\t\tkeypair, err := sourcesecret.LoadKeyPairFromPath(secretGitArgs.privateKeyFile, secretGitArgs.password)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\topts.Keypair = keypair\n\t\topts.SSHHostname = u.Host\n\t\topts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(secretGitArgs.keyAlgorithm)\n\t\topts.RSAKeyBits = int(secretGitArgs.rsaBits)\n\t\topts.ECDSACurve = secretGitArgs.ecdsaCurve.Curve\n\t\topts.Password = secretGitArgs.password\n\tcase \"http\", \"https\":\n\t\tif (secretGitArgs.username == \"\" || secretGitArgs.password == \"\") && secretGitArgs.bearerToken == \"\" {\n\t\t\treturn fmt.Errorf(\"for Git over HTTP/S the username and password, or a bearer token is required\")\n\t\t}\n\t\topts.Username = secretGitArgs.username\n\t\topts.Password = secretGitArgs.password\n\t\topts.BearerToken = secretGitArgs.bearerToken\n\t\tif secretGitArgs.username != \"\" && secretGitArgs.password != \"\" && secretGitArgs.bearerToken != \"\" {\n\t\t\treturn fmt.Errorf(\"user credentials and bearer token cannot be used together\")\n\t\t}\n\n\t\t// --ca-crt-file takes precedence over --ca-file.\n\t\tif secretGitArgs.caCrtFile != \"\" {\n\t\t\topts.CACrt, err = os.ReadFile(secretGitArgs.caCrtFile)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"unable to read TLS CA file: %w\", err)\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"git URL scheme '%s' not supported, can be: ssh, http and https\", u.Scheme)\n\t}\n\n\tsecret, err := sourcesecret.GenerateGit(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif createArgs.export {\n\t\trootCmd.Println(secret.Content)\n\t\treturn nil\n\t}\n\n\tvar s corev1.Secret\n\tif err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {\n\t\treturn err\n\t}\n\n\tif ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {\n\t\tlogger.Generatef(\"deploy key: %s\", ppk)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := upsertSecret(ctx, kubeClient, s); err != nil {\n\t\treturn err\n\t}\n\tlogger.Actionf(\"git secret '%s' created in '%s' namespace\", name, *kubeconfigArgs.Namespace)\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_git_test.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestCreateGitSecret(t *testing.T) {\n\tfile, err := os.CreateTemp(t.TempDir(), \"ca-crt\")\n\tif err != nil {\n\t\tt.Fatal(\"could not create CA certificate file\")\n\t}\n\t_, err = file.Write([]byte(\"ca-data\"))\n\tif err != nil {\n\t\tt.Fatal(\"could not write to CA certificate file\")\n\t}\n\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\tname:   \"no args\",\n\t\t\targs:   \"create secret git\",\n\t\t\tassert: assertError(\"name is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"basic secret\",\n\t\t\targs:   \"create secret git podinfo-auth --url=https://github.com/stefanprodan/podinfo --username=my-username --password=my-password --namespace=my-namespace --export\",\n\t\t\tassert: assertGoldenFile(\"./testdata/create_secret/git/secret-git-basic.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"ssh key\",\n\t\t\targs:   \"create secret git podinfo-auth --url=ssh://git@github.com/stefanprodan/podinfo --private-key-file=./testdata/create_secret/git/ecdsa.private --namespace=my-namespace --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_secret/git/git-ssh-secret.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"ssh key with password\",\n\t\t\targs:   \"create secret git podinfo-auth --url=ssh://git@github.com/stefanprodan/podinfo --private-key-file=./testdata/create_secret/git/ecdsa-password.private --password=password --namespace=my-namespace --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_secret/git/git-ssh-secret-password.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"git authentication with bearer token\",\n\t\t\targs:   \"create secret git bearer-token-auth --url=https://github.com/stefanprodan/podinfo --bearer-token=ghp_baR2qnFF0O41WlucePL3udt2N9vVZS4R0hAS --namespace=my-namespace --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_secret/git/git-bearer-token.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"git authentication with CA certificate\",\n\t\t\targs:   fmt.Sprintf(\"create secret git ca-crt --url=https://github.com/stefanprodan/podinfo --password=my-password --username=my-username --ca-crt-file=%s --namespace=my-namespace --export\", file.Name()),\n\t\t\tassert: assertGoldenFile(\"testdata/create_secret/git/secret-ca-crt.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"git authentication with basic auth and bearer token\",\n\t\t\targs:   \"create secret git podinfo-auth --url=https://github.com/stefanprodan/podinfo --username=aaa --password=zzzz --bearer-token=aaaa --namespace=my-namespace --export\",\n\t\t\tassert: assertError(\"user credentials and bearer token cannot be used together\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_github_app.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nvar createSecretGitHubAppCmd = &cobra.Command{\n\tUse:   \"githubapp [name]\",\n\tShort: \"Create or update a github app secret\",\n\tLong:  withPreviewNote(`The create secret githubapp command generates a Kubernetes secret that can be used for GitRepository authentication with github app`),\n\tExample: `  # Create a githubapp authentication secret on disk and encrypt it with Mozilla SOPS\n  flux create secret githubapp podinfo-auth \\\n    --app-id=\"1\" \\\n    --app-installation-id=\"2\" \\\n    --app-private-key=./private-key-file.pem \\\n    --export > githubapp-auth.yaml\n\n  sops --encrypt --encrypted-regex '^(data|stringData)$' \\\n    --in-place githubapp-auth.yaml\n\t`,\n\tRunE: createSecretGitHubAppCmdRun,\n}\n\ntype secretGitHubAppFlags struct {\n\tappID                string\n\tappInstallationOwner string\n\tappInstallationID    string\n\tprivateKeyFile       string\n\tbaseURL              string\n}\n\nvar secretGitHubAppArgs = secretGitHubAppFlags{}\n\nfunc init() {\n\tcreateSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.appID, \"app-id\", \"\", \"github app ID\")\n\tcreateSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.appInstallationOwner, \"app-installation-owner\", \"\", \"github app installation owner (user or organization)\")\n\tcreateSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.appInstallationID, \"app-installation-id\", \"\", \"github app installation ID\")\n\tcreateSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.privateKeyFile, \"app-private-key\", \"\", \"github app private key file path\")\n\tcreateSecretGitHubAppCmd.Flags().StringVar(&secretGitHubAppArgs.baseURL, \"app-base-url\", \"\", \"github app base URL\")\n\n\tcreateSecretCmd.AddCommand(createSecretGitHubAppCmd)\n}\n\nfunc createSecretGitHubAppCmdRun(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"name is required\")\n\t}\n\n\tsecretName := args[0]\n\n\tprivateKey, err := os.ReadFile(secretGitHubAppArgs.privateKeyFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to read private key file: %w\", err)\n\t}\n\n\topts := sourcesecret.Options{\n\t\tName:                       secretName,\n\t\tNamespace:                  *kubeconfigArgs.Namespace,\n\t\tGitHubAppID:                secretGitHubAppArgs.appID,\n\t\tGitHubAppInstallationOwner: secretGitHubAppArgs.appInstallationOwner,\n\t\tGitHubAppInstallationID:    secretGitHubAppArgs.appInstallationID,\n\t\tGitHubAppPrivateKey:        string(privateKey),\n\t\tGitHubAppBaseURL:           secretGitHubAppArgs.baseURL,\n\t}\n\n\tsecret, err := sourcesecret.GenerateGitHubApp(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif createArgs.export {\n\t\trootCmd.Println(secret.Content)\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar s corev1.Secret\n\tif err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {\n\t\treturn err\n\t}\n\tif err := upsertSecret(ctx, kubeClient, s); err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"githubapp secret '%s' created in '%s' namespace\", secretName, *kubeconfigArgs.Namespace)\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_githubapp_test.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCreateSecretGitHubApp(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\tname:   \"create githubapp secret with missing name\",\n\t\t\targs:   \"create secret githubapp\",\n\t\t\tassert: assertError(\"name is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"create githubapp secret with private key file that does not exist\",\n\t\t\targs:   \"create secret githubapp appinfo --app-id 1 --app-installation-id 2 --app-private-key pk.pem\",\n\t\t\tassert: assertError(\"unable to read private key file: open pk.pem: no such file or directory\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"create githubapp secret with app info\",\n\t\t\targs:   \"create secret githubapp appinfo --namespace my-namespace --app-id 1 --app-installation-owner my-org --app-private-key ./testdata/create_secret/githubapp/test-private-key.pem --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_secret/githubapp/secret.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"create githubapp secret with appinfo and base url\",\n\t\t\targs:   \"create secret githubapp appinfo --namespace my-namespace --app-id 1 --app-installation-id 2 --app-private-key ./testdata/create_secret/githubapp/test-private-key.pem --app-base-url www.example.com/api/v3 --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_secret/githubapp/secret-with-baseurl.yaml\"),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_helm.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n)\n\nvar createSecretHelmCmd = &cobra.Command{\n\tUse:   \"helm [name]\",\n\tShort: \"Create or update a Kubernetes secret for Helm repository authentication\",\n\tLong:  `The create secret helm command generates a Kubernetes secret with basic authentication credentials.`,\n\tExample: ` # Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS\n  flux create secret helm repo-auth \\\n    --namespace=my-namespace \\\n    --username=my-username \\\n    --password=my-password \\\n    --export > repo-auth.yaml\n\n  sops --encrypt --encrypted-regex '^(data|stringData)$' \\\n    --in-place repo-auth.yaml`,\n\n\tRunE: createSecretHelmCmdRun,\n}\n\ntype secretHelmFlags struct {\n\tusername string\n\tpassword string\n\tsecretTLSFlags\n}\n\nvar secretHelmArgs secretHelmFlags\n\nfunc init() {\n\tflags := createSecretHelmCmd.Flags()\n\tflags.StringVarP(&secretHelmArgs.username, \"username\", \"u\", \"\", \"basic authentication username\")\n\tflags.StringVarP(&secretHelmArgs.password, \"password\", \"p\", \"\", \"basic authentication password\")\n\tflags.StringVar(&secretHelmArgs.tlsCrtFile, \"tls-crt-file\", \"\", \"TLS authentication cert file path\")\n\tflags.StringVar(&secretHelmArgs.tlsKeyFile, \"tls-key-file\", \"\", \"TLS authentication key file path\")\n\tflags.StringVar(&secretHelmArgs.caCrtFile, \"ca-crt-file\", \"\", \"TLS authentication CA file path\")\n\n\tcreateSecretCmd.AddCommand(createSecretHelmCmd)\n}\n\nfunc createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tlabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaBundle := []byte{}\n\tif secretHelmArgs.caCrtFile != \"\" {\n\t\tvar err error\n\t\tcaBundle, err = os.ReadFile(secretHelmArgs.caCrtFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to read TLS CA file: %w\", err)\n\t\t}\n\t}\n\n\tvar certFile, keyFile []byte\n\tif secretHelmArgs.tlsCrtFile != \"\" {\n\t\tif certFile, err = os.ReadFile(secretHelmArgs.tlsCrtFile); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read cert file: %w\", err)\n\t\t}\n\t}\n\tif secretHelmArgs.tlsKeyFile != \"\" {\n\t\tif keyFile, err = os.ReadFile(secretHelmArgs.tlsKeyFile); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read key file: %w\", err)\n\t\t}\n\t}\n\n\topts := sourcesecret.Options{\n\t\tName:      name,\n\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\tLabels:    labels,\n\t\tUsername:  secretHelmArgs.username,\n\t\tPassword:  secretHelmArgs.password,\n\t\tCACrt:     caBundle,\n\t\tTLSCrt:    certFile,\n\t\tTLSKey:    keyFile,\n\t}\n\tsecret, err := sourcesecret.GenerateHelm(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif createArgs.export {\n\t\trootCmd.Println(secret.Content)\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar s corev1.Secret\n\tif err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {\n\t\treturn err\n\t}\n\tif err := upsertSecret(ctx, kubeClient, s); err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"helm secret '%s' created in '%s' namespace\", name, *kubeconfigArgs.Namespace)\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_helm_test.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCreateHelmSecret(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\targs:   \"create secret helm\",\n\t\t\tassert: assertError(\"name is required\"),\n\t\t},\n\t\t{\n\t\t\targs:   \"create secret helm helm-secret --username=my-username --password=my-password --namespace=my-namespace --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_secret/helm/secret-helm.yaml\"),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_notation.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/notaryproject/notation-go/verifier/trustpolicy\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nvar createSecretNotationCmd = &cobra.Command{\n\tUse:   \"notation [name]\",\n\tShort: \"Create or update a Kubernetes secret for verifications of artifacts signed by Notation\",\n\tLong:  withPreviewNote(`The create secret notation command generates a Kubernetes secret with root ca certificates and trust policy.`),\n\tExample: ` # Create a Notation configuration secret on disk and encrypt it with Mozilla SOPS\n  flux create secret notation my-notation-cert \\\n    --namespace=my-namespace \\\n    --trust-policy-file=./my-trust-policy.json \\\n    --ca-cert-file=./my-cert.crt \\\n    --export > my-notation-cert.yaml\n\n  sops --encrypt --encrypted-regex '^(data|stringData)$' \\\n    --in-place my-notation-cert.yaml`,\n\n\tRunE: createSecretNotationCmdRun,\n}\n\ntype secretNotationFlags struct {\n\ttrustPolicyFile string\n\tcaCrtFile       []string\n}\n\nvar secretNotationArgs secretNotationFlags\n\nfunc init() {\n\tcreateSecretNotationCmd.Flags().StringVar(&secretNotationArgs.trustPolicyFile, \"trust-policy-file\", \"\", \"notation trust policy file path\")\n\tcreateSecretNotationCmd.Flags().StringSliceVar(&secretNotationArgs.caCrtFile, \"ca-cert-file\", []string{}, \"root ca cert file path\")\n\n\tcreateSecretCmd.AddCommand(createSecretNotationCmd)\n}\n\nfunc createSecretNotationCmdRun(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"name is required\")\n\t}\n\n\tif secretNotationArgs.caCrtFile == nil || len(secretNotationArgs.caCrtFile) == 0 {\n\t\treturn fmt.Errorf(\"--ca-cert-file is required\")\n\t}\n\n\tif secretNotationArgs.trustPolicyFile == \"\" {\n\t\treturn fmt.Errorf(\"--trust-policy-file is required\")\n\t}\n\n\tname := args[0]\n\n\tlabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpolicy, err := os.ReadFile(secretNotationArgs.trustPolicyFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to read trust policy file: %w\", err)\n\t}\n\n\tvar doc trustpolicy.Document\n\n\tif err := json.Unmarshal(policy, &doc); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal trust policy %s: %w\", secretNotationArgs.trustPolicyFile, err)\n\t}\n\n\tif err := doc.Validate(); err != nil {\n\t\treturn fmt.Errorf(\"invalid trust policy: %w\", err)\n\t}\n\n\tvar (\n\t\tcaCerts []sourcesecret.VerificationCrt\n\t\tfileErr error\n\t)\n\tfor _, caCrtFile := range secretNotationArgs.caCrtFile {\n\t\tfileName := filepath.Base(caCrtFile)\n\t\tif !strings.HasSuffix(fileName, \".crt\") && !strings.HasSuffix(fileName, \".pem\") {\n\t\t\tfileErr = errors.Join(fileErr, fmt.Errorf(\"%s must end with either .crt or .pem\", fileName))\n\t\t\tcontinue\n\t\t}\n\t\tcaBundle, err := os.ReadFile(caCrtFile)\n\t\tif err != nil {\n\t\t\tfileErr = errors.Join(fileErr, fmt.Errorf(\"unable to read TLS CA file: %w\", err))\n\t\t\tcontinue\n\t\t}\n\t\tcaCerts = append(caCerts, sourcesecret.VerificationCrt{Name: fileName, CACrt: caBundle})\n\t}\n\n\tif fileErr != nil {\n\t\treturn fileErr\n\t}\n\n\tif len(caCerts) == 0 {\n\t\treturn fmt.Errorf(\"no CA certs found\")\n\t}\n\n\topts := sourcesecret.Options{\n\t\tName:             name,\n\t\tNamespace:        *kubeconfigArgs.Namespace,\n\t\tLabels:           labels,\n\t\tVerificationCrts: caCerts,\n\t\tTrustPolicy:      policy,\n\t}\n\tsecret, err := sourcesecret.GenerateNotation(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif createArgs.export {\n\t\trootCmd.Println(secret.Content)\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar s corev1.Secret\n\tif err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {\n\t\treturn err\n\t}\n\tif err := upsertSecret(ctx, kubeClient, s); err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"notation configuration secret '%s' created in '%s' namespace\", name, *kubeconfigArgs.Namespace)\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_notation_test.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nconst (\n\ttrustPolicy        = \"./testdata/create_secret/notation/test-trust-policy.json\"\n\tinvalidTrustPolicy = \"./testdata/create_secret/notation/invalid-trust-policy.json\"\n\tinvalidJson        = \"./testdata/create_secret/notation/invalid.json\"\n\ttestCertFolder     = \"./testdata/create_secret/notation\"\n)\n\nfunc TestCreateNotationSecret(t *testing.T) {\n\tcrt, err := os.Create(filepath.Join(t.TempDir(), \"ca.crt\"))\n\tif err != nil {\n\t\tt.Fatal(\"could not create ca.crt file\")\n\t}\n\n\tpem, err := os.Create(filepath.Join(t.TempDir(), \"ca.pem\"))\n\tif err != nil {\n\t\tt.Fatal(\"could not create ca.pem file\")\n\t}\n\n\tinvalidCert, err := os.Create(filepath.Join(t.TempDir(), \"ca.p12\"))\n\tif err != nil {\n\t\tt.Fatal(\"could not create ca.p12 file\")\n\t}\n\n\t_, err = crt.Write([]byte(\"ca-data-crt\"))\n\tif err != nil {\n\t\tt.Fatal(\"could not write to crt certificate file\")\n\t}\n\n\t_, err = pem.Write([]byte(\"ca-data-pem\"))\n\tif err != nil {\n\t\tt.Fatal(\"could not write to pem certificate file\")\n\t}\n\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\tname:   \"no args\",\n\t\t\targs:   \"create secret notation\",\n\t\t\tassert: assertError(\"name is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"no trust policy\",\n\t\t\targs:   fmt.Sprintf(\"create secret notation notation-config --ca-cert-file=%s\", testCertFolder),\n\t\t\tassert: assertError(\"--trust-policy-file is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"no cert\",\n\t\t\targs:   fmt.Sprintf(\"create secret notation notation-config --trust-policy-file=%s\", trustPolicy),\n\t\t\tassert: assertError(\"--ca-cert-file is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"non pem and crt cert\",\n\t\t\targs:   fmt.Sprintf(\"create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s\", invalidCert.Name(), trustPolicy),\n\t\t\tassert: assertError(\"ca.p12 must end with either .crt or .pem\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"invalid trust policy\",\n\t\t\targs:   fmt.Sprintf(\"create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s\", t.TempDir(), invalidTrustPolicy),\n\t\t\tassert: assertError(\"invalid trust policy: trust policy: a trust policy statement is missing a name, every statement requires a name\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"invalid trust policy json\",\n\t\t\targs:   fmt.Sprintf(\"create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s\", t.TempDir(), invalidJson),\n\t\t\tassert: assertError(fmt.Sprintf(\"failed to unmarshal trust policy %s: json: cannot unmarshal string into Go value of type trustpolicy.Document\", invalidJson)),\n\t\t},\n\t\t{\n\t\t\tname:   \"crt secret\",\n\t\t\targs:   fmt.Sprintf(\"create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s --namespace=my-namespace --export\", crt.Name(), trustPolicy),\n\t\t\tassert: assertGoldenFile(\"./testdata/create_secret/notation/secret-ca-crt.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"pem secret\",\n\t\t\targs:   fmt.Sprintf(\"create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s --namespace=my-namespace --export\", pem.Name(), trustPolicy),\n\t\t\tassert: assertGoldenFile(\"./testdata/create_secret/notation/secret-ca-pem.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"multi secret\",\n\t\t\targs:   fmt.Sprintf(\"create secret notation notation-config --ca-cert-file=%s --ca-cert-file=%s --trust-policy-file=%s --namespace=my-namespace --export\", crt.Name(), pem.Name(), trustPolicy),\n\t\t\tassert: assertGoldenFile(\"./testdata/create_secret/notation/secret-ca-multi.yaml\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tsecretNotationArgs = secretNotationFlags{}\n\t\t\t}()\n\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_oci.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/google/go-containerregistry/pkg/name\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nvar createSecretOCICmd = &cobra.Command{\n\tUse:   \"oci [name]\",\n\tShort: \"Create or update a Kubernetes image pull secret\",\n\tLong:  withPreviewNote(`The create secret oci command generates a Kubernetes secret that can be used for OCIRepository authentication`),\n\tExample: `  # Create an OCI authentication secret on disk and encrypt it with Mozilla SOPS\n  flux create secret oci podinfo-auth \\\n    --url=ghcr.io \\\n    --username=username \\\n    --password=password \\\n    --export > repo-auth.yaml\n\n  sops --encrypt --encrypted-regex '^(data|stringData)$' \\\n    --in-place repo-auth.yaml\n\t`,\n\tRunE: createSecretOCICmdRun,\n}\n\ntype secretOCIFlags struct {\n\turl      string\n\tpassword string\n\tusername string\n}\n\nvar secretOCIArgs = secretOCIFlags{}\n\nfunc init() {\n\tcreateSecretOCICmd.Flags().StringVar(&secretOCIArgs.url, \"url\", \"\", \"oci repository address e.g ghcr.io/stefanprodan/charts\")\n\tcreateSecretOCICmd.Flags().StringVarP(&secretOCIArgs.username, \"username\", \"u\", \"\", \"basic authentication username\")\n\tcreateSecretOCICmd.Flags().StringVarP(&secretOCIArgs.password, \"password\", \"p\", \"\", \"basic authentication password\")\n\n\tcreateSecretCmd.AddCommand(createSecretOCICmd)\n}\n\nfunc createSecretOCICmdRun(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"name is required\")\n\t}\n\n\tsecretName := args[0]\n\n\tif secretOCIArgs.url == \"\" {\n\t\treturn fmt.Errorf(\"--url is required\")\n\t}\n\n\tif secretOCIArgs.username == \"\" {\n\t\treturn fmt.Errorf(\"--username is required\")\n\t}\n\n\tif secretOCIArgs.password == \"\" {\n\t\treturn fmt.Errorf(\"--password is required\")\n\t}\n\n\tif _, err := name.ParseReference(secretOCIArgs.url); err != nil {\n\t\treturn fmt.Errorf(\"error parsing url: '%s'\", err)\n\t}\n\n\topts := sourcesecret.Options{\n\t\tName:      secretName,\n\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\tRegistry:  secretOCIArgs.url,\n\t\tPassword:  secretOCIArgs.password,\n\t\tUsername:  secretOCIArgs.username,\n\t}\n\n\tsecret, err := sourcesecret.GenerateOCI(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif createArgs.export {\n\t\trootCmd.Println(secret.Content)\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar s corev1.Secret\n\tif err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {\n\t\treturn err\n\t}\n\tif err := upsertSecret(ctx, kubeClient, s); err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"oci secret '%s' created in '%s' namespace\", secretName, *kubeconfigArgs.Namespace)\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_oci_test.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCreateSecretOCI(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\targs:   \"create secret oci\",\n\t\t\tassert: assertError(\"name is required\"),\n\t\t},\n\t\t{\n\t\t\targs:   \"create secret oci ghcr\",\n\t\t\tassert: assertError(\"--url is required\"),\n\t\t},\n\t\t{\n\t\t\targs:   \"create secret oci ghcr --namespace=my-namespace --url ghcr.io --username stefanprodan --password=password --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_secret/oci/create-secret.yaml\"),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_proxy.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n)\n\nvar createSecretProxyCmd = &cobra.Command{\n\tUse:   \"proxy [name]\",\n\tShort: \"Create or update a Kubernetes secret for proxy authentication\",\n\tLong: `The create secret proxy command generates a Kubernetes secret with the\nproxy address and the basic authentication credentials.`,\n\tExample: ` # Create a proxy secret on disk and encrypt it with SOPS\n  flux create secret proxy my-proxy \\\n    --namespace=my-namespace \\\n    --address=https://my-proxy.com \\\n    --username=my-username \\\n    --password=my-password \\\n    --export > proxy.yaml\n\n  sops --encrypt --encrypted-regex '^(data|stringData)$' \\\n    --in-place proxy.yaml`,\n\n\tRunE: createSecretProxyCmdRun,\n}\n\ntype secretProxyFlags struct {\n\taddress  string\n\tusername string\n\tpassword string\n}\n\nvar secretProxyArgs secretProxyFlags\n\nfunc init() {\n\tcreateSecretProxyCmd.Flags().StringVar(&secretProxyArgs.address, \"address\", \"\", \"proxy address\")\n\tcreateSecretProxyCmd.Flags().StringVarP(&secretProxyArgs.username, \"username\", \"u\", \"\", \"basic authentication username\")\n\tcreateSecretProxyCmd.Flags().StringVarP(&secretProxyArgs.password, \"password\", \"p\", \"\", \"basic authentication password\")\n\n\tcreateSecretCmd.AddCommand(createSecretProxyCmd)\n}\n\nfunc createSecretProxyCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tlabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif secretProxyArgs.address == \"\" {\n\t\treturn errors.New(\"address is required\")\n\t}\n\n\topts := sourcesecret.Options{\n\t\tName:      name,\n\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\tLabels:    labels,\n\t\tAddress:   secretProxyArgs.address,\n\t\tUsername:  secretProxyArgs.username,\n\t\tPassword:  secretProxyArgs.password,\n\t}\n\tsecret, err := sourcesecret.GenerateProxy(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif createArgs.export {\n\t\trootCmd.Println(secret.Content)\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar s corev1.Secret\n\tif err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {\n\t\treturn err\n\t}\n\tif err := upsertSecret(ctx, kubeClient, s); err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"proxy secret '%s' created in '%s' namespace\", name, *kubeconfigArgs.Namespace)\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_proxy_test.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCreateProxySecret(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\targs:   \"create secret proxy proxy-secret\",\n\t\t\tassert: assertError(\"address is required\"),\n\t\t},\n\t\t{\n\t\t\targs:   \"create secret proxy proxy-secret --address=https://my-proxy.com --username=my-username --password=my-password --namespace=my-namespace --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_secret/proxy/secret-proxy.yaml\"),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_tls.go",
    "content": "/*\nCopyright 2020, 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n)\n\nvar createSecretTLSCmd = &cobra.Command{\n\tUse:   \"tls [name]\",\n\tShort: \"Create or update a Kubernetes secret with TLS certificates\",\n\tLong:  `The create secret tls command generates a Kubernetes secret with certificates for use with TLS.`,\n\tExample: ` # Create a TLS secret on disk and encrypt it with SOPS.\n  # Files are expected to be PEM-encoded.\n  flux create secret tls certs \\\n    --namespace=my-namespace \\\n    --tls-crt-file=./client.crt \\\n    --tls-key-file=./client.key \\\n    --ca-crt-file=./ca.crt \\\n    --export > certs.yaml\n\n  sops --encrypt --encrypted-regex '^(data|stringData)$' \\\n    --in-place certs.yaml`,\n\tRunE: createSecretTLSCmdRun,\n}\n\ntype secretTLSFlags struct {\n\tcaCrtFile  string\n\ttlsKeyFile string\n\ttlsCrtFile string\n}\n\nvar secretTLSArgs secretTLSFlags\n\nfunc init() {\n\tcreateSecretTLSCmd.Flags().StringVar(&secretTLSArgs.tlsCrtFile, \"tls-crt-file\", \"\", \"TLS authentication cert file path\")\n\tcreateSecretTLSCmd.Flags().StringVar(&secretTLSArgs.tlsKeyFile, \"tls-key-file\", \"\", \"TLS authentication key file path\")\n\tcreateSecretTLSCmd.Flags().StringVar(&secretTLSArgs.caCrtFile, \"ca-crt-file\", \"\", \"TLS authentication CA file path\")\n\n\tcreateSecretCmd.AddCommand(createSecretTLSCmd)\n}\n\nfunc createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tlabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\topts := sourcesecret.Options{\n\t\tName:      name,\n\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\tLabels:    labels,\n\t}\n\n\tif secretTLSArgs.caCrtFile != \"\" {\n\t\topts.CACrt, err = os.ReadFile(secretTLSArgs.caCrtFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to read TLS CA file: %w\", err)\n\t\t}\n\t}\n\n\tif secretTLSArgs.tlsCrtFile != \"\" {\n\t\tif opts.TLSCrt, err = os.ReadFile(secretTLSArgs.tlsCrtFile); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read cert file: %w\", err)\n\t\t}\n\t}\n\tif secretTLSArgs.tlsKeyFile != \"\" {\n\t\tif opts.TLSKey, err = os.ReadFile(secretTLSArgs.tlsKeyFile); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read key file: %w\", err)\n\t\t}\n\t}\n\n\tsecret, err := sourcesecret.GenerateTLS(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif createArgs.export {\n\t\trootCmd.Print(secret.Content)\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar s corev1.Secret\n\tif err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {\n\t\treturn err\n\t}\n\tif err := upsertSecret(ctx, kubeClient, s); err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"tls secret '%s' created in '%s' namespace\", name, *kubeconfigArgs.Namespace)\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/create_secret_tls_test.go",
    "content": "package main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCreateTlsSecret(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\targs:   \"create secret tls\",\n\t\t\tassert: assertError(\"name is required\"),\n\t\t},\n\t\t{\n\t\t\targs:   \"create secret tls certs --namespace=my-namespace --tls-crt-file=./testdata/create_secret/tls/test-cert.pem --tls-key-file=./testdata/create_secret/tls/test-key.pem --ca-crt-file=./testdata/create_secret/tls/test-ca.pem --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_secret/tls/secret-tls.yaml\"),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/create_source.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar createSourceCmd = &cobra.Command{\n\tUse:   \"source\",\n\tShort: \"Create or update sources\",\n\tLong:  `The create source sub-commands generate sources.`,\n}\n\ntype createSourceFlags struct {\n\tfetchTimeout time.Duration\n}\n\nvar createSourceArgs createSourceFlags\n\nfunc init() {\n\tcreateSourceCmd.PersistentFlags().DurationVar(&createSourceArgs.fetchTimeout, \"fetch-timeout\", createSourceArgs.fetchTimeout,\n\t\t\"set a timeout for fetch operations performed by source-controller (e.g. 'git clone' or 'helm repo update')\")\n\tcreateCmd.AddCommand(createSourceCmd)\n}\n"
  },
  {
    "path": "cmd/flux/create_source_bucket.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar createSourceBucketCmd = &cobra.Command{\n\tUse:   \"bucket [name]\",\n\tShort: \"Create or update a Bucket source\",\n\tLong: `The create source bucket command generates a Bucket resource and waits for it to be downloaded.\nFor Buckets with static authentication, the credentials are stored in a Kubernetes secret.`,\n\tExample: `  # Create a source for a Bucket using static authentication\n  flux create source bucket podinfo \\\n\t--bucket-name=podinfo \\\n    --endpoint=minio.minio.svc.cluster.local:9000 \\\n\t--insecure=true \\\n\t--access-key=myaccesskey \\\n\t--secret-key=mysecretkey \\\n    --interval=10m\n\n  # Create a source for an Amazon S3 Bucket using IAM authentication\n  flux create source bucket podinfo \\\n\t--bucket-name=podinfo \\\n\t--provider=aws \\\n    --endpoint=s3.amazonaws.com \\\n\t--region=us-east-1 \\\n    --interval=10m`,\n\tRunE: createSourceBucketCmdRun,\n}\n\ntype sourceBucketFlags struct {\n\tname           string\n\tprovider       flags.SourceBucketProvider\n\tendpoint       string\n\taccessKey      string\n\tsecretKey      string\n\tregion         string\n\tinsecure       bool\n\tsecretRef      string\n\tproxySecretRef string\n\tignorePaths    []string\n}\n\nvar sourceBucketArgs = newSourceBucketFlags()\n\nfunc init() {\n\tcreateSourceBucketCmd.Flags().Var(&sourceBucketArgs.provider, \"provider\", sourceBucketArgs.provider.Description())\n\tcreateSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.name, \"bucket-name\", \"\", \"the bucket name\")\n\tcreateSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.endpoint, \"endpoint\", \"\", \"the bucket endpoint address\")\n\tcreateSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.accessKey, \"access-key\", \"\", \"the bucket access key\")\n\tcreateSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.secretKey, \"secret-key\", \"\", \"the bucket secret key\")\n\tcreateSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.region, \"region\", \"\", \"the bucket region\")\n\tcreateSourceBucketCmd.Flags().BoolVar(&sourceBucketArgs.insecure, \"insecure\", false, \"for when connecting to a non-TLS S3 HTTP endpoint\")\n\tcreateSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.secretRef, \"secret-ref\", \"\", \"the name of an existing secret containing credentials\")\n\tcreateSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.proxySecretRef, \"proxy-secret-ref\", \"\", \"the name of an existing secret containing the proxy address and credentials\")\n\tcreateSourceBucketCmd.Flags().StringSliceVar(&sourceBucketArgs.ignorePaths, \"ignore-paths\", nil, \"set paths to ignore in bucket resource (can specify multiple paths with commas: path1,path2)\")\n\n\tcreateSourceCmd.AddCommand(createSourceBucketCmd)\n}\n\nfunc newSourceBucketFlags() sourceBucketFlags {\n\treturn sourceBucketFlags{\n\t\tprovider: flags.SourceBucketProvider(sourcev1.BucketProviderGeneric),\n\t}\n}\n\nfunc createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tif sourceBucketArgs.name == \"\" {\n\t\treturn fmt.Errorf(\"bucket-name is required\")\n\t}\n\n\tif sourceBucketArgs.endpoint == \"\" {\n\t\treturn fmt.Errorf(\"endpoint is required\")\n\t}\n\n\tsourceLabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar ignorePaths *string\n\tif len(sourceBucketArgs.ignorePaths) > 0 {\n\t\tignorePathsStr := strings.Join(sourceBucketArgs.ignorePaths, \"\\n\")\n\t\tignorePaths = &ignorePathsStr\n\t}\n\n\tbucket := &sourcev1.Bucket{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    sourceLabels,\n\t\t},\n\t\tSpec: sourcev1.BucketSpec{\n\t\t\tBucketName: sourceBucketArgs.name,\n\t\t\tProvider:   sourceBucketArgs.provider.String(),\n\t\t\tInsecure:   sourceBucketArgs.insecure,\n\t\t\tEndpoint:   sourceBucketArgs.endpoint,\n\t\t\tRegion:     sourceBucketArgs.region,\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: createArgs.interval,\n\t\t\t},\n\t\t\tIgnore: ignorePaths,\n\t\t},\n\t}\n\n\tif createSourceArgs.fetchTimeout > 0 {\n\t\tbucket.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}\n\t}\n\n\tif sourceBucketArgs.secretRef != \"\" {\n\t\tbucket.Spec.SecretRef = &meta.LocalObjectReference{\n\t\t\tName: sourceBucketArgs.secretRef,\n\t\t}\n\t}\n\n\tif sourceBucketArgs.proxySecretRef != \"\" {\n\t\tbucket.Spec.ProxySecretRef = &meta.LocalObjectReference{\n\t\t\tName: sourceBucketArgs.proxySecretRef,\n\t\t}\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportBucket(bucket))\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Generatef(\"generating Bucket source\")\n\n\tif sourceBucketArgs.secretRef == \"\" {\n\t\tsecretName := fmt.Sprintf(\"bucket-%s\", name)\n\n\t\tsecret := corev1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      secretName,\n\t\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\t\tLabels:    sourceLabels,\n\t\t\t},\n\t\t\tStringData: map[string]string{},\n\t\t}\n\n\t\tif sourceBucketArgs.accessKey != \"\" && sourceBucketArgs.secretKey != \"\" {\n\t\t\tsecret.StringData[\"accesskey\"] = sourceBucketArgs.accessKey\n\t\t\tsecret.StringData[\"secretkey\"] = sourceBucketArgs.secretKey\n\t\t}\n\n\t\tif len(secret.StringData) > 0 {\n\t\t\tlogger.Actionf(\"applying secret with the bucket credentials\")\n\t\t\tif err := upsertSecret(ctx, kubeClient, secret); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tbucket.Spec.SecretRef = &meta.LocalObjectReference{\n\t\t\t\tName: secretName,\n\t\t\t}\n\t\t\tlogger.Successf(\"authentication configured\")\n\t\t}\n\t}\n\n\tlogger.Actionf(\"applying Bucket source\")\n\tnamespacedName, err := upsertBucket(ctx, kubeClient, bucket)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Waitingf(\"waiting for Bucket source reconciliation\")\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,\n\t\tisObjectReadyConditionFunc(kubeClient, namespacedName, bucket)); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"Bucket source reconciliation completed\")\n\n\tif bucket.Status.Artifact == nil {\n\t\treturn fmt.Errorf(\"Bucket source reconciliation but no artifact was found\")\n\t}\n\tlogger.Successf(\"fetched revision: %s\", bucket.Status.Artifact.Revision)\n\treturn nil\n}\n\nfunc upsertBucket(ctx context.Context, kubeClient client.Client,\n\tbucket *sourcev1.Bucket) (types.NamespacedName, error) {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: bucket.GetNamespace(),\n\t\tName:      bucket.GetName(),\n\t}\n\n\tvar existing sourcev1.Bucket\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, bucket); err != nil {\n\t\t\t\treturn namespacedName, err\n\t\t\t} else {\n\t\t\t\tlogger.Successf(\"Bucket source created\")\n\t\t\t\treturn namespacedName, nil\n\t\t\t}\n\t\t}\n\t\treturn namespacedName, err\n\t}\n\n\texisting.Labels = bucket.Labels\n\texisting.Spec = bucket.Spec\n\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\treturn namespacedName, err\n\t}\n\tbucket = &existing\n\tlogger.Successf(\"Bucket source updated\")\n\treturn namespacedName, nil\n}\n"
  },
  {
    "path": "cmd/flux/create_source_chart.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar createSourceChartCmd = &cobra.Command{\n\tUse:   \"chart [name]\",\n\tShort: \"Create or update a HelmChart source\",\n\tLong:  `The create source chart command generates a HelmChart resource and waits for the chart to be available.`,\n\tExample: `  # Create a source for a chart residing in a HelmRepository\n  flux create source chart podinfo \\\n    --source=HelmRepository/podinfo \\\n    --chart=podinfo \\\n    --chart-version=6.x\n\n  # Create a source for a chart residing in a Git repository\n  flux create source chart podinfo \\\n    --source=GitRepository/podinfo \\\n    --chart=./charts/podinfo\n\n  # Create a source for a chart residing in a S3 Bucket\n  flux create source chart podinfo \\\n    --source=Bucket/podinfo \\\n    --chart=./charts/podinfo\n\n  # Create a source for a chart from OCI and verify its signature\n  flux create source chart podinfo \\\n    --source HelmRepository/podinfo \\\n    --chart podinfo \\\n    --chart-version=6.6.2 \\\n    --verify-provider=cosign \\\n    --verify-issuer=https://token.actions.githubusercontent.com \\\n    --verify-subject=https://github.com/stefanprodan/podinfo/.github/workflows/release.yml@refs/tags/6.6.2`,\n\tRunE: createSourceChartCmdRun,\n}\n\ntype sourceChartFlags struct {\n\tchart             string\n\tchartVersion      string\n\tsource            flags.LocalHelmChartSource\n\treconcileStrategy string\n\tverifyProvider    flags.SourceOCIVerifyProvider\n\tverifySecretRef   string\n\tverifyOIDCIssuer  string\n\tverifySubject     string\n}\n\nvar sourceChartArgs sourceChartFlags\n\nfunc init() {\n\tcreateSourceChartCmd.Flags().StringVar(&sourceChartArgs.chart, \"chart\", \"\", \"Helm chart name or path\")\n\tcreateSourceChartCmd.Flags().StringVar(&sourceChartArgs.chartVersion, \"chart-version\", \"\", \"Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)\")\n\tcreateSourceChartCmd.Flags().Var(&sourceChartArgs.source, \"source\", sourceChartArgs.source.Description())\n\tcreateSourceChartCmd.Flags().StringVar(&sourceChartArgs.reconcileStrategy, \"reconcile-strategy\", \"ChartVersion\", \"the reconcile strategy for helm chart (accepted values: Revision and ChartRevision)\")\n\tcreateSourceChartCmd.Flags().Var(&sourceChartArgs.verifyProvider, \"verify-provider\", sourceOCIRepositoryArgs.verifyProvider.Description())\n\tcreateSourceChartCmd.Flags().StringVar(&sourceChartArgs.verifySecretRef, \"verify-secret-ref\", \"\", \"the name of a secret to use for signature verification\")\n\tcreateSourceChartCmd.Flags().StringVar(&sourceChartArgs.verifySubject, \"verify-subject\", \"\", \"regular expression to use for the OIDC subject during signature verification\")\n\tcreateSourceChartCmd.Flags().StringVar(&sourceChartArgs.verifyOIDCIssuer, \"verify-issuer\", \"\", \"regular expression to use for the OIDC issuer during signature verification\")\n\n\tcreateSourceCmd.AddCommand(createSourceChartCmd)\n}\n\nfunc createSourceChartCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tif sourceChartArgs.source.Kind == \"\" || sourceChartArgs.source.Name == \"\" {\n\t\treturn fmt.Errorf(\"chart source is required\")\n\t}\n\n\tif sourceChartArgs.chart == \"\" {\n\t\treturn fmt.Errorf(\"chart name or path is required\")\n\t}\n\n\tlogger.Generatef(\"generating HelmChart source\")\n\n\tsourceLabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\thelmChart := &sourcev1.HelmChart{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    sourceLabels,\n\t\t},\n\t\tSpec: sourcev1.HelmChartSpec{\n\t\t\tChart:   sourceChartArgs.chart,\n\t\t\tVersion: sourceChartArgs.chartVersion,\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: createArgs.interval,\n\t\t\t},\n\t\t\tReconcileStrategy: sourceChartArgs.reconcileStrategy,\n\t\t\tSourceRef: sourcev1.LocalHelmChartSourceReference{\n\t\t\t\tKind: sourceChartArgs.source.Kind,\n\t\t\t\tName: sourceChartArgs.source.Name,\n\t\t\t},\n\t\t},\n\t}\n\n\tif provider := sourceChartArgs.verifyProvider.String(); provider != \"\" {\n\t\thelmChart.Spec.Verify = &sourcev1.OCIRepositoryVerification{\n\t\t\tProvider: provider,\n\t\t}\n\t\tif secretName := sourceChartArgs.verifySecretRef; secretName != \"\" {\n\t\t\thelmChart.Spec.Verify.SecretRef = &meta.LocalObjectReference{\n\t\t\t\tName: secretName,\n\t\t\t}\n\t\t}\n\t\tverifyIssuer := sourceChartArgs.verifyOIDCIssuer\n\t\tverifySubject := sourceChartArgs.verifySubject\n\t\tif verifyIssuer != \"\" || verifySubject != \"\" {\n\t\t\thelmChart.Spec.Verify.MatchOIDCIdentity = []sourcev1.OIDCIdentityMatch{{\n\t\t\t\tIssuer:  verifyIssuer,\n\t\t\t\tSubject: verifySubject,\n\t\t\t}}\n\t\t}\n\t} else if sourceChartArgs.verifySecretRef != \"\" {\n\t\treturn fmt.Errorf(\"a verification provider must be specified when a secret is specified\")\n\t} else if sourceChartArgs.verifyOIDCIssuer != \"\" || sourceOCIRepositoryArgs.verifySubject != \"\" {\n\t\treturn fmt.Errorf(\"a verification provider must be specified when OIDC issuer/subject is specified\")\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportHelmChart(helmChart))\n\t}\n\n\tlogger.Actionf(\"applying HelmChart source\")\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnamespacedName, err := upsertHelmChart(ctx, kubeClient, helmChart)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Waitingf(\"waiting for HelmChart source reconciliation\")\n\treadyConditionFunc := isObjectReadyConditionFunc(kubeClient, namespacedName, helmChart)\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, readyConditionFunc); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"HelmChart source reconciliation completed\")\n\n\tif helmChart.Status.Artifact == nil {\n\t\treturn fmt.Errorf(\"HelmChart source reconciliation completed but no artifact was found\")\n\t}\n\tlogger.Successf(\"fetched revision: %s\", helmChart.Status.Artifact.Revision)\n\treturn nil\n}\n\nfunc upsertHelmChart(ctx context.Context, kubeClient client.Client,\n\thelmChart *sourcev1.HelmChart) (types.NamespacedName, error) {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: helmChart.GetNamespace(),\n\t\tName:      helmChart.GetName(),\n\t}\n\n\tvar existing sourcev1.HelmChart\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, helmChart); err != nil {\n\t\t\t\treturn namespacedName, err\n\t\t\t} else {\n\t\t\t\tlogger.Successf(\"source created\")\n\t\t\t\treturn namespacedName, nil\n\t\t\t}\n\t\t}\n\t\treturn namespacedName, err\n\t}\n\n\texisting.Labels = helmChart.Labels\n\texisting.Spec = helmChart.Spec\n\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\treturn namespacedName, err\n\t}\n\thelmChart = &existing\n\tlogger.Successf(\"source updated\")\n\treturn namespacedName, nil\n}\n"
  },
  {
    "path": "cmd/flux/create_source_chart_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport \"testing\"\n\nfunc TestCreateSourceChart(t *testing.T) {\n\ttmpl := map[string]string{\n\t\t\"fluxns\": allocateNamespace(\"flux-system\"),\n\t}\n\tsetupSourceChart(t, tmpl)\n\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\tname:   \"missing name\",\n\t\t\targs:   \"create source chart --export\",\n\t\t\tassert: assertError(\"name is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"missing source reference\",\n\t\t\targs:   \"create source chart podinfo --export \",\n\t\t\tassert: assertError(\"chart source is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"missing chart name\",\n\t\t\targs:   \"create source chart podinfo --source helmrepository/podinfo --export\",\n\t\t\tassert: assertError(\"chart name or path is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"unknown source kind\",\n\t\t\targs:   \"create source chart podinfo --source foobar/podinfo --export\",\n\t\t\tassert: assertError(`invalid argument \"foobar/podinfo\" for \"--source\" flag: source kind 'foobar' is not supported, must be one of: HelmRepository, GitRepository, Bucket`),\n\t\t},\n\t\t{\n\t\t\tname:   \"basic chart\",\n\t\t\targs:   \"create source chart podinfo --source helmrepository/podinfo --chart podinfo --export\",\n\t\t\tassert: assertGoldenTemplateFile(\"testdata/create_source_chart/basic.yaml\", tmpl),\n\t\t},\n\t\t{\n\t\t\tname:   \"chart with basic signature verification\",\n\t\t\targs:   \"create source chart podinfo --source helmrepository/podinfo --chart podinfo --verify-provider cosign --export\",\n\t\t\tassert: assertGoldenTemplateFile(\"testdata/create_source_chart/verify_basic.yaml\", tmpl),\n\t\t},\n\t\t{\n\t\t\tname:   \"unknown signature verification provider\",\n\t\t\targs:   \"create source chart podinfo --source helmrepository/podinfo --chart podinfo --verify-provider foobar --export\",\n\t\t\tassert: assertError(`invalid argument \"foobar\" for \"--verify-provider\" flag: source OCI verify provider 'foobar' is not supported, must be one of: cosign`),\n\t\t},\n\t\t{\n\t\t\tname:   \"chart with complete signature verification\",\n\t\t\targs:   \"create source chart podinfo --source helmrepository/podinfo --chart podinfo --verify-provider cosign --verify-issuer foo --verify-subject bar --export\",\n\t\t\tassert: assertGoldenTemplateFile(\"testdata/create_source_chart/verify_complete.yaml\", tmpl),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args + \" -n \" + tmpl[\"fluxns\"],\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n\nfunc setupSourceChart(t *testing.T, tmpl map[string]string) {\n\tt.Helper()\n\ttestEnv.CreateObjectFile(\"./testdata/create_source_chart/setup-source.yaml\", tmpl, t)\n}\n"
  },
  {
    "path": "cmd/flux/create_source_git.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"crypto/elliptic\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/manifoldco/promptui\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n)\n\ntype sourceGitFlags struct {\n\turl                 string\n\tbranch              string\n\ttag                 string\n\tsemver              string\n\trefName             string\n\tcommit              string\n\tusername            string\n\tpassword            string\n\tkeyAlgorithm        flags.PublicKeyAlgorithm\n\tkeyRSABits          flags.RSAKeyBits\n\tkeyECDSACurve       flags.ECDSACurve\n\tsecretRef           string\n\tproxySecretRef      string\n\tprovider            flags.SourceGitProvider\n\tcaFile              string\n\tprivateKeyFile      string\n\trecurseSubmodules   bool\n\tsilent              bool\n\tignorePaths         []string\n\tsparseCheckoutPaths []string\n}\n\nvar createSourceGitCmd = &cobra.Command{\n\tUse:   \"git [name]\",\n\tShort: \"Create or update a GitRepository source\",\n\tLong: `The create source git command generates a GitRepository resource and waits for it to sync.\nFor Git over SSH, host and SSH keys are automatically generated and stored in a Kubernetes secret.\nFor private Git repositories, the basic authentication credentials are stored in a Kubernetes secret.`,\n\tExample: `  # Create a source from a public Git repository master branch\n  flux create source git podinfo \\\n    --url=https://github.com/stefanprodan/podinfo \\\n    --branch=master\n\n  # Create a source for a Git repository pinned to specific git tag\n  flux create source git podinfo \\\n    --url=https://github.com/stefanprodan/podinfo \\\n    --tag=\"3.2.3\"\n\n  # Create a source from a public Git repository tag that matches a semver range\n  flux create source git podinfo \\\n    --url=https://github.com/stefanprodan/podinfo \\\n    --tag-semver=\">=3.2.0 <3.3.0\"\n\n  # Create a source for a Git repository using SSH authentication\n  flux create source git podinfo \\\n    --url=ssh://git@github.com/stefanprodan/podinfo \\\n    --branch=master\n\n  # Create a source for a Git repository using SSH authentication and an\n  # ECDSA P-521 curve public key\n  flux create source git podinfo \\\n    --url=ssh://git@github.com/stefanprodan/podinfo \\\n    --branch=master \\\n    --ssh-key-algorithm=ecdsa \\\n    --ssh-ecdsa-curve=p521\n\n  # Create a source for a Git repository using SSH authentication and a\n  #\tpasswordless private key from file\n  # The public SSH host key will still be gathered from the host\n  flux create source git podinfo \\\n    --url=ssh://git@github.com/stefanprodan/podinfo \\\n    --branch=master \\\n    --private-key-file=./private.key\n\n  # Create a source for a Git repository using SSH authentication and a\n  # private key with a password from file\n  # The public SSH host key will still be gathered from the host\n  flux create source git podinfo \\\n    --url=ssh://git@github.com/stefanprodan/podinfo \\\n    --branch=master \\\n    --private-key-file=./private.key \\\n    --password=<password>\n\n  # Create a source for a Git repository using basic authentication\n  flux create source git podinfo \\\n    --url=https://github.com/stefanprodan/podinfo \\\n    --branch=master \\\n    --username=username \\\n    --password=password\n\n  # Create a source for a Git repository using azure provider\n  flux create source git podinfo \\\n    --url=https://dev.azure.com/foo/bar/_git/podinfo \\\n    --branch=master \\\n    --provider=azure`,\n\tRunE: createSourceGitCmdRun,\n}\n\nvar sourceGitArgs = newSourceGitFlags()\n\nfunc init() {\n\tcreateSourceGitCmd.Flags().StringVar(&sourceGitArgs.url, \"url\", \"\", \"git address, e.g. ssh://git@host/org/repository\")\n\tcreateSourceGitCmd.Flags().StringVar(&sourceGitArgs.branch, \"branch\", \"\", \"git branch\")\n\tcreateSourceGitCmd.Flags().StringVar(&sourceGitArgs.tag, \"tag\", \"\", \"git tag\")\n\tcreateSourceGitCmd.Flags().StringVar(&sourceGitArgs.semver, \"tag-semver\", \"\", \"git tag semver range\")\n\tcreateSourceGitCmd.Flags().StringVar(&sourceGitArgs.refName, \"ref-name\", \"\", \"git reference name\")\n\tcreateSourceGitCmd.Flags().StringVar(&sourceGitArgs.commit, \"commit\", \"\", \"git commit\")\n\tcreateSourceGitCmd.Flags().StringVarP(&sourceGitArgs.username, \"username\", \"u\", \"\", \"basic authentication username\")\n\tcreateSourceGitCmd.Flags().StringVarP(&sourceGitArgs.password, \"password\", \"p\", \"\", \"basic authentication password\")\n\tcreateSourceGitCmd.Flags().Var(&sourceGitArgs.keyAlgorithm, \"ssh-key-algorithm\", sourceGitArgs.keyAlgorithm.Description())\n\tcreateSourceGitCmd.Flags().Var(&sourceGitArgs.keyRSABits, \"ssh-rsa-bits\", sourceGitArgs.keyRSABits.Description())\n\tcreateSourceGitCmd.Flags().Var(&sourceGitArgs.keyECDSACurve, \"ssh-ecdsa-curve\", sourceGitArgs.keyECDSACurve.Description())\n\tcreateSourceGitCmd.Flags().StringVar(&sourceGitArgs.secretRef, \"secret-ref\", \"\", \"the name of an existing secret containing SSH or basic credentials or github app authentication\")\n\tcreateSourceGitCmd.Flags().StringVar(&sourceGitArgs.proxySecretRef, \"proxy-secret-ref\", \"\", \"the name of an existing secret containing the proxy address and credentials\")\n\tcreateSourceGitCmd.Flags().Var(&sourceGitArgs.provider, \"provider\", sourceGitArgs.provider.Description())\n\tcreateSourceGitCmd.Flags().StringVar(&sourceGitArgs.caFile, \"ca-file\", \"\", \"path to TLS CA file used for validating self-signed certificates\")\n\tcreateSourceGitCmd.Flags().StringVar(&sourceGitArgs.privateKeyFile, \"private-key-file\", \"\", \"path to a passwordless private key file used for authenticating to the Git SSH server\")\n\tcreateSourceGitCmd.Flags().BoolVar(&sourceGitArgs.recurseSubmodules, \"recurse-submodules\", false,\n\t\t\"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces\")\n\tcreateSourceGitCmd.Flags().BoolVarP(&sourceGitArgs.silent, \"silent\", \"s\", false, \"assumes the deploy key is already setup, skips confirmation\")\n\tcreateSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.ignorePaths, \"ignore-paths\", nil, \"set paths to ignore in git resource (can specify multiple paths with commas: path1,path2)\")\n\tcreateSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.sparseCheckoutPaths, \"sparse-checkout-paths\", nil, \"set paths to sparse checkout in git resource (can specify multiple paths with commas: path1,path2)\")\n\n\tcreateSourceCmd.AddCommand(createSourceGitCmd)\n}\n\nfunc newSourceGitFlags() sourceGitFlags {\n\treturn sourceGitFlags{\n\t\tkeyAlgorithm:  flags.PublicKeyAlgorithm(sourcesecret.ECDSAPrivateKeyAlgorithm),\n\t\tkeyRSABits:    2048,\n\t\tkeyECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()},\n\t}\n}\n\nfunc createSourceGitCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tif sourceGitArgs.url == \"\" {\n\t\treturn fmt.Errorf(\"url is required\")\n\t}\n\n\tu, err := url.Parse(sourceGitArgs.url)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"git URL parse failed: %w\", err)\n\t}\n\tif u.Scheme != \"ssh\" && u.Scheme != \"http\" && u.Scheme != \"https\" {\n\t\treturn fmt.Errorf(\"git URL scheme '%s' not supported, can be: ssh, http and https\", u.Scheme)\n\t}\n\n\tif sourceGitArgs.branch == \"\" && sourceGitArgs.tag == \"\" && sourceGitArgs.semver == \"\" && sourceGitArgs.commit == \"\" && sourceGitArgs.refName == \"\" {\n\t\treturn fmt.Errorf(\"a Git ref is required, use one of the following: --branch, --tag, --commit, --ref-name or --tag-semver\")\n\t}\n\n\tif sourceGitArgs.caFile != \"\" && u.Scheme == \"ssh\" {\n\t\treturn fmt.Errorf(\"specifying a CA file is not supported for Git over SSH\")\n\t}\n\n\tsourceLabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar ignorePaths *string\n\tif len(sourceGitArgs.ignorePaths) > 0 {\n\t\tignorePathsStr := strings.Join(sourceGitArgs.ignorePaths, \"\\n\")\n\t\tignorePaths = &ignorePathsStr\n\t}\n\n\tgitRepository := sourcev1.GitRepository{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    sourceLabels,\n\t\t},\n\t\tSpec: sourcev1.GitRepositorySpec{\n\t\t\tURL: sourceGitArgs.url,\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: createArgs.interval,\n\t\t\t},\n\t\t\tRecurseSubmodules: sourceGitArgs.recurseSubmodules,\n\t\t\tReference:         &sourcev1.GitRepositoryRef{},\n\t\t\tIgnore:            ignorePaths,\n\t\t\tSparseCheckout:    sourceGitArgs.sparseCheckoutPaths,\n\t\t},\n\t}\n\n\tif createSourceArgs.fetchTimeout > 0 {\n\t\tgitRepository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}\n\t}\n\n\tif sourceGitArgs.commit != \"\" {\n\t\tgitRepository.Spec.Reference.Commit = sourceGitArgs.commit\n\t\tgitRepository.Spec.Reference.Branch = sourceGitArgs.branch\n\t} else if sourceGitArgs.refName != \"\" {\n\t\tgitRepository.Spec.Reference.Name = sourceGitArgs.refName\n\t} else if sourceGitArgs.semver != \"\" {\n\t\tgitRepository.Spec.Reference.SemVer = sourceGitArgs.semver\n\t} else if sourceGitArgs.tag != \"\" {\n\t\tgitRepository.Spec.Reference.Tag = sourceGitArgs.tag\n\t} else {\n\t\tgitRepository.Spec.Reference.Branch = sourceGitArgs.branch\n\t}\n\n\tif sourceGitArgs.secretRef != \"\" {\n\t\tgitRepository.Spec.SecretRef = &meta.LocalObjectReference{\n\t\t\tName: sourceGitArgs.secretRef,\n\t\t}\n\t}\n\n\tif sourceGitArgs.proxySecretRef != \"\" {\n\t\tgitRepository.Spec.ProxySecretRef = &meta.LocalObjectReference{\n\t\t\tName: sourceGitArgs.proxySecretRef,\n\t\t}\n\t}\n\n\tif provider := sourceGitArgs.provider.String(); provider != \"\" {\n\t\tgitRepository.Spec.Provider = provider\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportGit(&gitRepository))\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Generatef(\"generating GitRepository source\")\n\tif sourceGitArgs.secretRef == \"\" {\n\t\tsecretOpts := sourcesecret.Options{\n\t\t\tName:         name,\n\t\t\tNamespace:    *kubeconfigArgs.Namespace,\n\t\t\tManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,\n\t\t}\n\t\tswitch u.Scheme {\n\t\tcase \"ssh\":\n\t\t\tkeypair, err := sourcesecret.LoadKeyPairFromPath(sourceGitArgs.privateKeyFile, sourceGitArgs.password)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tsecretOpts.Keypair = keypair\n\t\t\tsecretOpts.SSHHostname = u.Host\n\t\t\tsecretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(sourceGitArgs.keyAlgorithm)\n\t\t\tsecretOpts.RSAKeyBits = int(sourceGitArgs.keyRSABits)\n\t\t\tsecretOpts.ECDSACurve = sourceGitArgs.keyECDSACurve.Curve\n\t\t\tsecretOpts.Password = sourceGitArgs.password\n\t\tcase \"https\":\n\t\t\tif sourceGitArgs.caFile != \"\" {\n\t\t\t\tcaBundle, err := os.ReadFile(sourceGitArgs.caFile)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unable to read TLS CA file: %w\", err)\n\t\t\t\t}\n\t\t\t\tsecretOpts.CACrt = caBundle\n\t\t\t}\n\t\t\tsecretOpts.Username = sourceGitArgs.username\n\t\t\tsecretOpts.Password = sourceGitArgs.password\n\t\tcase \"http\":\n\t\t\tlogger.Warningf(\"insecure configuration: credentials configured for an HTTP URL\")\n\t\t\tsecretOpts.Username = sourceGitArgs.username\n\t\t\tsecretOpts.Password = sourceGitArgs.password\n\t\t}\n\t\tsecret, err := sourcesecret.GenerateGit(secretOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvar s corev1.Secret\n\t\tif err = yaml.Unmarshal([]byte(secret.Content), &s); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(s.StringData) > 0 {\n\t\t\tif hk, ok := s.StringData[sourcesecret.KnownHostsSecretKey]; ok {\n\t\t\t\tlogger.Successf(\"collected public key from SSH server:\\n%s\", hk)\n\t\t\t}\n\t\t\tif ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {\n\t\t\t\tlogger.Generatef(\"deploy key: %s\", ppk)\n\t\t\t\tif !sourceGitArgs.silent {\n\t\t\t\t\tprompt := promptui.Prompt{\n\t\t\t\t\t\tLabel:     \"Have you added the deploy key to your repository\",\n\t\t\t\t\t\tIsConfirm: true,\n\t\t\t\t\t}\n\t\t\t\t\tif _, err := prompt.Run(); err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"aborting\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlogger.Actionf(\"applying secret with repository credentials\")\n\t\t\tif err := upsertSecret(ctx, kubeClient, s); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tgitRepository.Spec.SecretRef = &meta.LocalObjectReference{\n\t\t\t\tName: s.Name,\n\t\t\t}\n\t\t\tlogger.Successf(\"authentication configured\")\n\t\t}\n\t}\n\n\tlogger.Actionf(\"applying GitRepository source\")\n\tnamespacedName, err := upsertGitRepository(ctx, kubeClient, &gitRepository)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Waitingf(\"waiting for GitRepository source reconciliation\")\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,\n\t\tisObjectReadyConditionFunc(kubeClient, namespacedName, &gitRepository)); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"GitRepository source reconciliation completed\")\n\n\tif gitRepository.Status.Artifact == nil {\n\t\treturn fmt.Errorf(\"GitRepository source reconciliation completed but no artifact was found\")\n\t}\n\tlogger.Successf(\"fetched revision: %s\", gitRepository.Status.Artifact.Revision)\n\treturn nil\n}\n\nfunc upsertGitRepository(ctx context.Context, kubeClient client.Client,\n\tgitRepository *sourcev1.GitRepository) (types.NamespacedName, error) {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: gitRepository.GetNamespace(),\n\t\tName:      gitRepository.GetName(),\n\t}\n\n\tvar existing sourcev1.GitRepository\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, gitRepository); err != nil {\n\t\t\t\treturn namespacedName, err\n\t\t\t} else {\n\t\t\t\tlogger.Successf(\"GitRepository source created\")\n\t\t\t\treturn namespacedName, nil\n\t\t\t}\n\t\t}\n\t\treturn namespacedName, err\n\t}\n\n\texisting.Labels = gitRepository.Labels\n\texisting.Spec = gitRepository.Spec\n\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\treturn namespacedName, err\n\t}\n\tgitRepository = &existing\n\tlogger.Successf(\"GitRepository source updated\")\n\treturn namespacedName, nil\n}\n"
  },
  {
    "path": "cmd/flux/create_source_git_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tapimeta \"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar pollInterval = 50 * time.Millisecond\nvar testTimeout = 10 * time.Second\n\n// Update the GitRepository once created to exercise test specific behavior\ntype reconcileFunc func(repo *sourcev1.GitRepository)\n\n// reconciler waits for an object to be created, then invokes a test supplied\n// function to mutate that object, simulating a controller.\n// Test should invoke run() to run the background reconciler task which\n// polls to wait for the object to exist before applying the update function.\n// Any errors from the reconciler are asserted on test completion.\ntype reconciler struct {\n\tclient    client.Client\n\tname      types.NamespacedName\n\treconcile reconcileFunc\n}\n\n// Start the background task that waits for the object to exist then applies\n// the update function.\nfunc (r *reconciler) run(t *testing.T) {\n\tresult := make(chan error)\n\tgo func() {\n\t\tdefer close(result)\n\t\terr := wait.PollImmediate(\n\t\t\tpollInterval,\n\t\t\ttestTimeout,\n\t\t\tr.conditionFunc)\n\t\tresult <- err\n\t}()\n\tt.Cleanup(func() {\n\t\tif err := <-result; err != nil {\n\t\t\tt.Errorf(\"Failure from test reconciler: '%v':\", err.Error())\n\t\t}\n\t})\n}\n\n// A ConditionFunction that waits for the named GitRepository to be created,\n// then sets the ready condition to true.\nfunc (r *reconciler) conditionFunc() (bool, error) {\n\tvar repo sourcev1.GitRepository\n\tif err := r.client.Get(context.Background(), r.name, &repo); err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\treturn false, nil // Keep polling until object is created\n\t\t}\n\t\treturn true, err\n\t}\n\tr.reconcile(&repo)\n\terr := r.client.Status().Update(context.Background(), &repo)\n\treturn true, err\n}\n\nfunc TestCreateSourceGitExport(t *testing.T) {\n\tvar command = \"create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --sparse-checkout-paths .cosign,non-existent-dir/ --ignore-paths .cosign,non-existent-dir/ -n default --interval 1m --export --timeout=\" + testTimeout.String()\n\n\tcases := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\t\"ExportSucceeded\",\n\t\t\tcommand,\n\t\t\tassertGoldenFile(\"testdata/create_source_git/export.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"no args\",\n\t\t\targs:   \"create source git --url=https://github.com/stefanprodan/podinfo\",\n\t\t\tassert: assertError(\"name is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with commit\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --commit=c88a2f41 --interval=1m0s --export\",\n\t\t\tassert: assertGoldenFile(\"./testdata/create_source_git/source-git-commit.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with ref name\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --ref-name=refs/heads/main --interval=1m0s --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_source_git/source-git-refname.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with branch name and commit\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --branch=main --commit=c88a2f41 --interval=1m0s --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_source_git/source-git-branch-commit.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with semver\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --tag-semver=v1.01 --interval=1m0s --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_source_git/source-git-semver.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with git tag\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --tag=test --interval=1m0s --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_source_git/source-git-tag.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with git branch\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --branch=test --interval=1m0s --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_source_git/source-git-branch.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with generic provider\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --provider generic --branch=test --interval=1m0s --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_source_git/source-git-provider-generic.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with azure provider\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --provider azure --branch=test --interval=1m0s --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_source_git/source-git-provider-azure.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with github provider\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --provider github --branch=test --interval=1m0s --secret-ref appinfo --export\",\n\t\t\tassert: assertGoldenFile(\"testdata/create_source_git/source-git-provider-github.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with invalid provider\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --provider dummy --branch=test --interval=1m0s --export\",\n\t\t\tassert: assertError(\"invalid argument \\\"dummy\\\" for \\\"--provider\\\" flag: source Git provider 'dummy' is not supported, must be one of: generic|azure|github\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with empty provider\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --provider \\\"\\\" --branch=test --interval=1m0s --export\",\n\t\t\tassert: assertError(\"invalid argument \\\"\\\" for \\\"--provider\\\" flag: no source Git provider given, please specify the Git provider name\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"source with no provider\",\n\t\t\targs:   \"create source git podinfo --namespace=flux-system --url=https://dev.azure.com/foo/bar/_git/podinfo --branch=test --interval=1m0s --export --provider\",\n\t\t\tassert: assertError(\"flag needs an argument: --provider\"),\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tc.args,\n\t\t\t\tassert: tc.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n\nfunc TestCreateSourceGit(t *testing.T) {\n\t// Default command used for multiple tests\n\tvar command = \"create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --timeout=\" + testTimeout.String()\n\n\tcases := []struct {\n\t\tname      string\n\t\targs      string\n\t\tassert    assertFunc\n\t\treconcile reconcileFunc\n\t}{\n\t\t{\n\t\t\t\"NoArgs\",\n\t\t\t\"create source git\",\n\t\t\tassertError(\"name is required\"),\n\t\t\tnil,\n\t\t}, {\n\t\t\t\"Succeeded\",\n\t\t\tcommand,\n\t\t\tassertGoldenFile(\"testdata/create_source_git/success.golden\"),\n\t\t\tfunc(repo *sourcev1.GitRepository) {\n\t\t\t\tnewCondition := metav1.Condition{\n\t\t\t\t\tType:               meta.ReadyCondition,\n\t\t\t\t\tStatus:             metav1.ConditionTrue,\n\t\t\t\t\tReason:             sourcev1.GitOperationSucceedReason,\n\t\t\t\t\tMessage:            \"succeeded message\",\n\t\t\t\t\tObservedGeneration: repo.GetGeneration(),\n\t\t\t\t}\n\t\t\t\tapimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)\n\t\t\t\trepo.Status.Artifact = &meta.Artifact{\n\t\t\t\t\tPath:     \"some-path\",\n\t\t\t\t\tRevision: \"v1\",\n\t\t\t\t\tLastUpdateTime: metav1.Time{\n\t\t\t\t\t\tTime: time.Now(),\n\t\t\t\t\t},\n\t\t\t\t\tDigest: \"sha256:1234567890abcdef\",\n\t\t\t\t}\n\t\t\t\trepo.Status.ObservedGeneration = repo.GetGeneration()\n\t\t\t},\n\t\t}, {\n\t\t\t\"Failed\",\n\t\t\tcommand,\n\t\t\tassertError(\"failed message\"),\n\t\t\tfunc(repo *sourcev1.GitRepository) {\n\t\t\t\tstalledCondition := metav1.Condition{\n\t\t\t\t\tType:               meta.StalledCondition,\n\t\t\t\t\tStatus:             metav1.ConditionTrue,\n\t\t\t\t\tReason:             sourcev1.URLInvalidReason,\n\t\t\t\t\tMessage:            \"failed message\",\n\t\t\t\t\tObservedGeneration: repo.GetGeneration(),\n\t\t\t\t}\n\t\t\t\tapimeta.SetStatusCondition(&repo.Status.Conditions, stalledCondition)\n\t\t\t\tnewCondition := metav1.Condition{\n\t\t\t\t\tType:               meta.ReadyCondition,\n\t\t\t\t\tStatus:             metav1.ConditionFalse,\n\t\t\t\t\tReason:             sourcev1.URLInvalidReason,\n\t\t\t\t\tMessage:            \"failed message\",\n\t\t\t\t\tObservedGeneration: repo.GetGeneration(),\n\t\t\t\t}\n\t\t\t\tapimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)\n\t\t\t\trepo.Status.ObservedGeneration = repo.GetGeneration()\n\t\t\t},\n\t\t}, {\n\t\t\t\"NoArtifact\",\n\t\t\tcommand,\n\t\t\tassertError(\"GitRepository source reconciliation completed but no artifact was found\"),\n\t\t\tfunc(repo *sourcev1.GitRepository) {\n\t\t\t\t// Updated with no artifact\n\t\t\t\tnewCondition := metav1.Condition{\n\t\t\t\t\tType:               meta.ReadyCondition,\n\t\t\t\t\tStatus:             metav1.ConditionTrue,\n\t\t\t\t\tReason:             sourcev1.GitOperationSucceedReason,\n\t\t\t\t\tMessage:            \"succeeded message\",\n\t\t\t\t\tObservedGeneration: repo.GetGeneration(),\n\t\t\t\t}\n\t\t\t\tapimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)\n\t\t\t\trepo.Status.ObservedGeneration = repo.GetGeneration()\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tns := allocateNamespace(\"podinfo\")\n\t\t\tsetupTestNamespace(ns, t)\n\t\t\tif tc.reconcile != nil {\n\t\t\t\tr := reconciler{\n\t\t\t\t\tclient:    testEnv.client,\n\t\t\t\t\tname:      types.NamespacedName{Namespace: ns, Name: \"podinfo\"},\n\t\t\t\t\treconcile: tc.reconcile,\n\t\t\t\t}\n\t\t\t\tr.run(t)\n\t\t\t}\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tc.args + \" -n=\" + ns,\n\t\t\t\tassert: tc.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/create_source_helm.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n)\n\nvar createSourceHelmCmd = &cobra.Command{\n\tUse:   \"helm [name]\",\n\tShort: \"Create or update a HelmRepository source\",\n\tLong: `The create source helm command generates a HelmRepository resource and waits for it to fetch the index.\nFor private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`,\n\tExample: `  # Create a source for an HTTPS public Helm repository\n  flux create source helm podinfo \\\n    --url=https://stefanprodan.github.io/podinfo \\\n    --interval=10m\n\n  # Create a source for an HTTPS Helm repository using basic authentication\n  flux create source helm podinfo \\\n    --url=https://stefanprodan.github.io/podinfo \\\n    --username=username \\\n    --password=password\n\n  # Create a source for an HTTPS Helm repository using TLS authentication\n  flux create source helm podinfo \\\n    --url=https://stefanprodan.github.io/podinfo \\\n    --cert-file=./cert.crt \\\n    --key-file=./key.crt \\\n    --ca-file=./ca.crt\n\n  # Create a source for an OCI Helm repository\n  flux create source helm podinfo \\\n    --url=oci://ghcr.io/stefanprodan/charts/podinfo \\\n    --username=username \\\n    --password=password\n\n  # Create a source for an OCI Helm repository using an existing secret with basic auth or dockerconfig credentials\n  flux create source helm podinfo \\\n    --url=oci://ghcr.io/stefanprodan/charts/podinfo \\\n    --secret-ref=docker-config`,\n\tRunE: createSourceHelmCmdRun,\n}\n\ntype sourceHelmFlags struct {\n\turl             string\n\tusername        string\n\tpassword        string\n\tcertFile        string\n\tkeyFile         string\n\tcaFile          string\n\tsecretRef       string\n\tociProvider     string\n\tpassCredentials bool\n}\n\nvar sourceHelmArgs sourceHelmFlags\n\nfunc init() {\n\tcreateSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.url, \"url\", \"\", \"Helm repository address\")\n\tcreateSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.username, \"username\", \"u\", \"\", \"basic authentication username\")\n\tcreateSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.password, \"password\", \"p\", \"\", \"basic authentication password\")\n\tcreateSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.certFile, \"cert-file\", \"\", \"TLS authentication cert file path\")\n\tcreateSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.keyFile, \"key-file\", \"\", \"TLS authentication key file path\")\n\tcreateSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.caFile, \"ca-file\", \"\", \"TLS authentication CA file path\")\n\tcreateSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.secretRef, \"secret-ref\", \"\", \"\", \"the name of an existing secret containing TLS, basic auth or docker-config credentials\")\n\tcreateSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.ociProvider, \"oci-provider\", \"\", \"OCI provider for authentication\")\n\tcreateSourceHelmCmd.Flags().BoolVarP(&sourceHelmArgs.passCredentials, \"pass-credentials\", \"\", false, \"pass credentials to all domains\")\n\n\tcreateSourceCmd.AddCommand(createSourceHelmCmd)\n}\n\nfunc createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tif sourceHelmArgs.url == \"\" {\n\t\treturn fmt.Errorf(\"url is required\")\n\t}\n\n\tsourceLabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := url.Parse(sourceHelmArgs.url); err != nil {\n\t\treturn fmt.Errorf(\"url parse failed: %w\", err)\n\t}\n\n\thelmRepository := &sourcev1.HelmRepository{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    sourceLabels,\n\t\t},\n\t\tSpec: sourcev1.HelmRepositorySpec{\n\t\t\tURL: sourceHelmArgs.url,\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: createArgs.interval,\n\t\t\t},\n\t\t},\n\t}\n\n\turl, err := url.Parse(sourceHelmArgs.url)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse URL: %w\", err)\n\t}\n\tif url.Scheme == sourcev1.HelmRepositoryTypeOCI {\n\t\thelmRepository.Spec.Type = sourcev1.HelmRepositoryTypeOCI\n\t\thelmRepository.Spec.Provider = sourceHelmArgs.ociProvider\n\t}\n\n\tif createSourceArgs.fetchTimeout > 0 {\n\t\thelmRepository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}\n\t}\n\n\tif sourceHelmArgs.secretRef != \"\" {\n\t\thelmRepository.Spec.SecretRef = &meta.LocalObjectReference{\n\t\t\tName: sourceHelmArgs.secretRef,\n\t\t}\n\t\thelmRepository.Spec.PassCredentials = sourceHelmArgs.passCredentials\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportHelmRepository(helmRepository))\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaBundle := []byte{}\n\tif sourceHelmArgs.caFile != \"\" {\n\t\tvar err error\n\t\tcaBundle, err = os.ReadFile(sourceHelmArgs.caFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to read TLS CA file: %w\", err)\n\t\t}\n\t}\n\n\tvar certFile, keyFile []byte\n\tif sourceHelmArgs.certFile != \"\" && sourceHelmArgs.keyFile != \"\" {\n\t\tif certFile, err = os.ReadFile(sourceHelmArgs.certFile); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read cert file: %w\", err)\n\t\t}\n\t\tif keyFile, err = os.ReadFile(sourceHelmArgs.keyFile); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read key file: %w\", err)\n\t\t}\n\t}\n\n\tlogger.Generatef(\"generating HelmRepository source\")\n\tif sourceHelmArgs.secretRef == \"\" {\n\t\tsecretName := fmt.Sprintf(\"helm-%s\", name)\n\t\tsecretOpts := sourcesecret.Options{\n\t\t\tName:         secretName,\n\t\t\tNamespace:    *kubeconfigArgs.Namespace,\n\t\t\tUsername:     sourceHelmArgs.username,\n\t\t\tPassword:     sourceHelmArgs.password,\n\t\t\tCACrt:        caBundle,\n\t\t\tTLSCrt:       certFile,\n\t\t\tTLSKey:       keyFile,\n\t\t\tManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,\n\t\t}\n\t\tsecret, err := sourcesecret.GenerateHelm(secretOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvar s corev1.Secret\n\t\tif err = yaml.Unmarshal([]byte(secret.Content), &s); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(s.StringData) > 0 {\n\t\t\tlogger.Actionf(\"applying secret with repository credentials\")\n\t\t\tif err := upsertSecret(ctx, kubeClient, s); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\thelmRepository.Spec.SecretRef = &meta.LocalObjectReference{\n\t\t\t\tName: secretName,\n\t\t\t}\n\t\t\thelmRepository.Spec.PassCredentials = sourceHelmArgs.passCredentials\n\t\t\tlogger.Successf(\"authentication configured\")\n\t\t}\n\t}\n\n\tlogger.Actionf(\"applying HelmRepository source\")\n\tnamespacedName, err := upsertHelmRepository(ctx, kubeClient, helmRepository)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Waitingf(\"waiting for HelmRepository source reconciliation\")\n\treadyConditionFunc := isObjectReadyConditionFunc(kubeClient, namespacedName, helmRepository)\n\tif helmRepository.Spec.Type == sourcev1.HelmRepositoryTypeOCI {\n\t\t// HelmRepository type OCI is a static object.\n\t\treadyConditionFunc = isStaticObjectReadyConditionFunc(kubeClient, namespacedName, helmRepository)\n\t}\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, readyConditionFunc); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"HelmRepository source reconciliation completed\")\n\n\tif helmRepository.Spec.Type == sourcev1.HelmRepositoryTypeOCI {\n\t\t// OCI repos don't expose any artifact so we just return early here\n\t\treturn nil\n\t}\n\n\tif helmRepository.Status.Artifact == nil {\n\t\treturn fmt.Errorf(\"HelmRepository source reconciliation completed but no artifact was found\")\n\t}\n\tlogger.Successf(\"fetched revision: %s\", helmRepository.Status.Artifact.Revision)\n\treturn nil\n}\n\nfunc upsertHelmRepository(ctx context.Context, kubeClient client.Client,\n\thelmRepository *sourcev1.HelmRepository) (types.NamespacedName, error) {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: helmRepository.GetNamespace(),\n\t\tName:      helmRepository.GetName(),\n\t}\n\n\tvar existing sourcev1.HelmRepository\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, helmRepository); err != nil {\n\t\t\t\treturn namespacedName, err\n\t\t\t} else {\n\t\t\t\tlogger.Successf(\"source created\")\n\t\t\t\treturn namespacedName, nil\n\t\t\t}\n\t\t}\n\t\treturn namespacedName, err\n\t}\n\n\texisting.Labels = helmRepository.Labels\n\texisting.Spec = helmRepository.Spec\n\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\treturn namespacedName, err\n\t}\n\thelmRepository = &existing\n\tlogger.Successf(\"source updated\")\n\treturn namespacedName, nil\n}\n"
  },
  {
    "path": "cmd/flux/create_source_helm_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCreateSourceHelm(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\targs       string\n\t\tresultFile string\n\t\tassertFunc string\n\t}{\n\t\t{\n\t\t\tname:       \"no args\",\n\t\t\targs:       \"create source helm\",\n\t\t\tresultFile: \"name is required\",\n\t\t\tassertFunc: \"assertError\",\n\t\t},\n\t\t{\n\t\t\tname:       \"OCI repo\",\n\t\t\targs:       \"create source helm podinfo --url=oci://ghcr.io/stefanprodan/charts/podinfo --interval 5m --export\",\n\t\t\tresultFile: \"./testdata/create_source_helm/oci.golden\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"OCI repo with Secret ref\",\n\t\t\targs:       \"create source helm podinfo --url=oci://ghcr.io/stefanprodan/charts/podinfo --interval 5m --secret-ref=creds --export\",\n\t\t\tresultFile: \"./testdata/create_source_helm/oci-with-secret.golden\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t\t{\n\t\t\tname:       \"HTTPS repo\",\n\t\t\targs:       \"create source helm podinfo --url=https://stefanprodan.github.io/charts/podinfo --interval 5m --export\",\n\t\t\tresultFile: \"./testdata/create_source_helm/https.golden\",\n\t\t\tassertFunc: \"assertGoldenTemplateFile\",\n\t\t},\n\t}\n\n\ttmpl := map[string]string{\n\t\t\"fluxns\": allocateNamespace(\"flux-system\"),\n\t}\n\tsetup(t, tmpl)\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar assert assertFunc\n\t\t\tswitch tt.assertFunc {\n\t\t\tcase \"assertGoldenTemplateFile\":\n\t\t\t\tassert = assertGoldenTemplateFile(tt.resultFile, tmpl)\n\t\t\tcase \"assertError\":\n\t\t\t\tassert = assertError(tt.resultFile)\n\t\t\t}\n\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args + \" -n \" + tmpl[\"fluxns\"],\n\t\t\t\tassert: assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/create_source_oci.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar createSourceOCIRepositoryCmd = &cobra.Command{\n\tUse:   \"oci [name]\",\n\tShort: \"Create or update an OCIRepository\",\n\tLong:  withPreviewNote(`The create source oci command generates an OCIRepository resource and waits for it to be ready.`),\n\tExample: `  # Create an OCIRepository for a public container image\n  flux create source oci podinfo \\\n    --url=oci://ghcr.io/stefanprodan/manifests/podinfo \\\n    --tag=6.6.2 \\\n    --interval=10m\n\n  # Create an OCIRepository with OIDC signature verification\n  flux create source oci podinfo \\\n    --url=oci://ghcr.io/stefanprodan/manifests/podinfo \\\n    --tag=6.6.2 \\\n    --interval=10m \\\n    --verify-provider=cosign \\\n    --verify-subject=\"^https://github.com/stefanprodan/podinfo/.github/workflows/release.yml@refs/tags/6.6.2$\" \\\n    --verify-issuer=\"^https://token.actions.githubusercontent.com$\"\n`,\n\tRunE: createSourceOCIRepositoryCmdRun,\n}\n\ntype sourceOCIRepositoryFlags struct {\n\turl              string\n\ttag              string\n\tsemver           string\n\tdigest           string\n\tsecretRef        string\n\tproxySecretRef   string\n\tserviceAccount   string\n\tcertSecretRef    string\n\tverifyProvider   flags.SourceOCIVerifyProvider\n\tverifySecretRef  string\n\tverifyOIDCIssuer string\n\tverifySubject    string\n\tignorePaths      []string\n\tprovider         flags.SourceOCIProvider\n\tinsecure         bool\n}\n\nvar sourceOCIRepositoryArgs = newSourceOCIFlags()\n\nfunc newSourceOCIFlags() sourceOCIRepositoryFlags {\n\treturn sourceOCIRepositoryFlags{\n\t\tprovider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),\n\t}\n}\n\nfunc init() {\n\tcreateSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.provider, \"provider\", sourceOCIRepositoryArgs.provider.Description())\n\tcreateSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.url, \"url\", \"\", \"the OCI repository URL\")\n\tcreateSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.tag, \"tag\", \"\", \"the OCI artifact tag\")\n\tcreateSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.semver, \"tag-semver\", \"\", \"the OCI artifact tag semver range\")\n\tcreateSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.digest, \"digest\", \"\", \"the OCI artifact digest\")\n\tcreateSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, \"secret-ref\", \"\", \"the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')\")\n\tcreateSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.proxySecretRef, \"proxy-secret-ref\", \"\", \"the name of an existing secret containing the proxy address and credentials\")\n\tcreateSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.serviceAccount, \"service-account\", \"\", \"the name of the Kubernetes service account that refers to an image pull secret\")\n\tcreateSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, \"cert-ref\", \"\", \"the name of a secret to use for TLS certificates\")\n\tcreateSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.verifyProvider, \"verify-provider\", sourceOCIRepositoryArgs.verifyProvider.Description())\n\tcreateSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifySecretRef, \"verify-secret-ref\", \"\", \"the name of a secret to use for signature verification\")\n\tcreateSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifySubject, \"verify-subject\", \"\", \"regular expression to use for the OIDC subject during signature verification\")\n\tcreateSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifyOIDCIssuer, \"verify-issuer\", \"\", \"regular expression to use for the OIDC issuer during signature verification\")\n\tcreateSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, \"ignore-paths\", nil, \"set paths to ignore resources (can specify multiple paths with commas: path1,path2)\")\n\tcreateSourceOCIRepositoryCmd.Flags().BoolVar(&sourceOCIRepositoryArgs.insecure, \"insecure\", false, \"for when connecting to a non-TLS registries over plain HTTP\")\n\n\tcreateSourceCmd.AddCommand(createSourceOCIRepositoryCmd)\n}\n\nfunc createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tif sourceOCIRepositoryArgs.url == \"\" {\n\t\treturn fmt.Errorf(\"url is required\")\n\t}\n\n\tif sourceOCIRepositoryArgs.semver == \"\" && sourceOCIRepositoryArgs.tag == \"\" && sourceOCIRepositoryArgs.digest == \"\" {\n\t\treturn fmt.Errorf(\"--tag, --tag-semver or --digest is required\")\n\t}\n\n\tsourceLabels, err := parseLabels()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar ignorePaths *string\n\tif len(sourceOCIRepositoryArgs.ignorePaths) > 0 {\n\t\tignorePathsStr := strings.Join(sourceOCIRepositoryArgs.ignorePaths, \"\\n\")\n\t\tignorePaths = &ignorePathsStr\n\t}\n\n\trepository := &sourcev1.OCIRepository{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tLabels:    sourceLabels,\n\t\t},\n\t\tSpec: sourcev1.OCIRepositorySpec{\n\t\t\tProvider: sourceOCIRepositoryArgs.provider.String(),\n\t\t\tURL:      sourceOCIRepositoryArgs.url,\n\t\t\tInsecure: sourceOCIRepositoryArgs.insecure,\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: createArgs.interval,\n\t\t\t},\n\t\t\tReference: &sourcev1.OCIRepositoryRef{},\n\t\t\tIgnore:    ignorePaths,\n\t\t},\n\t}\n\n\tif digest := sourceOCIRepositoryArgs.digest; digest != \"\" {\n\t\trepository.Spec.Reference.Digest = digest\n\t}\n\tif semver := sourceOCIRepositoryArgs.semver; semver != \"\" {\n\t\trepository.Spec.Reference.SemVer = semver\n\t}\n\tif tag := sourceOCIRepositoryArgs.tag; tag != \"\" {\n\t\trepository.Spec.Reference.Tag = tag\n\t}\n\n\tif createSourceArgs.fetchTimeout > 0 {\n\t\trepository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}\n\t}\n\n\tif saName := sourceOCIRepositoryArgs.serviceAccount; saName != \"\" {\n\t\trepository.Spec.ServiceAccountName = saName\n\t}\n\n\tif secretName := sourceOCIRepositoryArgs.secretRef; secretName != \"\" {\n\t\trepository.Spec.SecretRef = &meta.LocalObjectReference{\n\t\t\tName: secretName,\n\t\t}\n\t}\n\n\tif secretName := sourceOCIRepositoryArgs.proxySecretRef; secretName != \"\" {\n\t\trepository.Spec.ProxySecretRef = &meta.LocalObjectReference{\n\t\t\tName: secretName,\n\t\t}\n\t}\n\n\tif secretName := sourceOCIRepositoryArgs.certSecretRef; secretName != \"\" {\n\t\trepository.Spec.CertSecretRef = &meta.LocalObjectReference{\n\t\t\tName: secretName,\n\t\t}\n\t}\n\n\tif provider := sourceOCIRepositoryArgs.verifyProvider.String(); provider != \"\" {\n\t\trepository.Spec.Verify = &sourcev1.OCIRepositoryVerification{\n\t\t\tProvider: provider,\n\t\t}\n\t\tif secretName := sourceOCIRepositoryArgs.verifySecretRef; secretName != \"\" {\n\t\t\trepository.Spec.Verify.SecretRef = &meta.LocalObjectReference{\n\t\t\t\tName: secretName,\n\t\t\t}\n\t\t}\n\t\tverifyIssuer := sourceOCIRepositoryArgs.verifyOIDCIssuer\n\t\tverifySubject := sourceOCIRepositoryArgs.verifySubject\n\t\tif verifyIssuer != \"\" || verifySubject != \"\" {\n\t\t\trepository.Spec.Verify.MatchOIDCIdentity = []sourcev1.OIDCIdentityMatch{{\n\t\t\t\tIssuer:  verifyIssuer,\n\t\t\t\tSubject: verifySubject,\n\t\t\t}}\n\t\t}\n\t} else if sourceOCIRepositoryArgs.verifySecretRef != \"\" {\n\t\treturn fmt.Errorf(\"a verification provider must be specified when a secret is specified\")\n\t} else if sourceOCIRepositoryArgs.verifyOIDCIssuer != \"\" || sourceOCIRepositoryArgs.verifySubject != \"\" {\n\t\treturn fmt.Errorf(\"a verification provider must be specified when OIDC issuer/subject is specified\")\n\t}\n\n\tif createArgs.export {\n\t\treturn printExport(exportOCIRepository(repository))\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Actionf(\"applying OCIRepository\")\n\tnamespacedName, err := upsertOCIRepository(ctx, kubeClient, repository)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Waitingf(\"waiting for OCIRepository reconciliation\")\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,\n\t\tisObjectReadyConditionFunc(kubeClient, namespacedName, repository)); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"OCIRepository reconciliation completed\")\n\n\tif repository.Status.Artifact == nil {\n\t\treturn fmt.Errorf(\"no artifact was found\")\n\t}\n\tlogger.Successf(\"fetched revision: %s\", repository.Status.Artifact.Revision)\n\treturn nil\n}\n\nfunc upsertOCIRepository(ctx context.Context, kubeClient client.Client,\n\tociRepository *sourcev1.OCIRepository) (types.NamespacedName, error) {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: ociRepository.GetNamespace(),\n\t\tName:      ociRepository.GetName(),\n\t}\n\n\tvar existing sourcev1.OCIRepository\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, ociRepository); err != nil {\n\t\t\t\treturn namespacedName, err\n\t\t\t} else {\n\t\t\t\tlogger.Successf(\"OCIRepository created\")\n\t\t\t\treturn namespacedName, nil\n\t\t\t}\n\t\t}\n\t\treturn namespacedName, err\n\t}\n\n\texisting.Labels = ociRepository.Labels\n\texisting.Spec = ociRepository.Spec\n\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\treturn namespacedName, err\n\t}\n\tociRepository = &existing\n\tlogger.Successf(\"OCIRepository updated\")\n\treturn namespacedName, nil\n}\n"
  },
  {
    "path": "cmd/flux/create_source_oci_test.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCreateSourceOCI(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\targs       string\n\t\tassertFunc assertFunc\n\t}{\n\t\t{\n\t\t\tname:       \"NoArgs\",\n\t\t\targs:       \"create source oci\",\n\t\t\tassertFunc: assertError(\"name is required\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"NoURL\",\n\t\t\targs:       \"create source oci podinfo\",\n\t\t\tassertFunc: assertError(\"url is required\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"verify secret specified but provider missing\",\n\t\t\targs:       \"create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-secret-ref=cosign-pub\",\n\t\t\tassertFunc: assertError(\"a verification provider must be specified when a secret is specified\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"verify issuer specified but provider missing\",\n\t\t\targs:       \"create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-issuer=github.com\",\n\t\t\tassertFunc: assertError(\"a verification provider must be specified when OIDC issuer/subject is specified\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"verify identity specified but provider missing\",\n\t\t\targs:       \"create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-subject=developer\",\n\t\t\tassertFunc: assertError(\"a verification provider must be specified when OIDC issuer/subject is specified\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"verify issuer specified but subject missing\",\n\t\t\targs:       \"create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-issuer=github --verify-provider=cosign --export\",\n\t\t\tassertFunc: assertGoldenFile(\"./testdata/oci/export_with_issuer.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"all verify fields set\",\n\t\t\targs:       \"create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-issuer=github verify-subject=stefanprodan --verify-provider=cosign --export\",\n\t\t\tassertFunc: assertGoldenFile(\"./testdata/oci/export_with_issuer.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"verify subject specified but issuer missing\",\n\t\t\targs:       \"create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-subject=stefanprodan --verify-provider=cosign --export\",\n\t\t\tassertFunc: assertGoldenFile(\"./testdata/oci/export_with_subject.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"export manifest\",\n\t\t\targs:       \"create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --export\",\n\t\t\tassertFunc: assertGoldenFile(\"./testdata/oci/export.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"export manifest with secret\",\n\t\t\targs:       \"create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --secret-ref=creds --export\",\n\t\t\tassertFunc: assertGoldenFile(\"./testdata/oci/export_with_secret.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"export manifest with verify secret\",\n\t\t\targs:       \"create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --verify-provider=cosign --verify-secret-ref=cosign-pub --export\",\n\t\t\tassertFunc: assertGoldenFile(\"./testdata/oci/export_with_verify_secret.golden\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assertFunc,\n\t\t\t}\n\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/create_tenant.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\trbacv1 \"k8s.io/api/rbac/v1\"\n\t\"k8s.io/apimachinery/pkg/api/equality\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar createTenantCmd = &cobra.Command{\n\tUse:   \"tenant\",\n\tShort: \"Create or update a tenant\",\n\tLong: withPreviewNote(`The create tenant command generates namespaces, service accounts and role bindings to limit the\nreconcilers scope to the tenant namespaces.`),\n\tExample: `  # Create a tenant with access to a namespace \n  flux create tenant dev-team \\\n    --with-namespace=frontend \\\n    --label=environment=dev\n\n  # Generate tenant namespaces and role bindings in YAML format\n  flux create tenant dev-team \\\n    --with-namespace=frontend \\\n    --with-namespace=backend \\\n\t--export > dev-team.yaml`,\n\tRunE: createTenantCmdRun,\n}\n\nconst (\n\ttenantLabel = \"toolkit.fluxcd.io/tenant\"\n)\n\ntype tenantFlags struct {\n\tnamespaces    []string\n\tclusterRole   string\n\taccount       string\n\tskipNamespace bool\n}\n\nvar tenantArgs tenantFlags\n\nfunc init() {\n\tcreateTenantCmd.Flags().StringSliceVar(&tenantArgs.namespaces, \"with-namespace\", nil, \"namespace belonging to this tenant\")\n\tcreateTenantCmd.Flags().StringVar(&tenantArgs.clusterRole, \"cluster-role\", \"cluster-admin\", \"cluster role of the tenant role binding\")\n\tcreateTenantCmd.Flags().StringVar(&tenantArgs.account, \"with-service-account\", \"\", \"service account belonging to this tenant\")\n\tcreateTenantCmd.Flags().BoolVar(&tenantArgs.skipNamespace, \"skip-namespace\", false, \"skip namespace creation (namespace must exist already)\")\n\tcreateCmd.AddCommand(createTenantCmd)\n}\n\nfunc createTenantCmdRun(cmd *cobra.Command, args []string) error {\n\ttenant := args[0]\n\tif err := validation.IsQualifiedName(tenant); len(err) > 0 {\n\t\treturn fmt.Errorf(\"invalid tenant name '%s': %v\", tenant, err)\n\t}\n\n\tif tenantArgs.clusterRole == \"\" {\n\t\treturn fmt.Errorf(\"cluster-role is required\")\n\t}\n\n\tif tenantArgs.namespaces == nil {\n\t\treturn fmt.Errorf(\"with-namespace is required\")\n\t}\n\n\tvar namespaces []corev1.Namespace\n\tvar accounts []corev1.ServiceAccount\n\tvar roleBindings []rbacv1.RoleBinding\n\n\tfor _, ns := range tenantArgs.namespaces {\n\t\tif err := validation.IsQualifiedName(ns); len(err) > 0 {\n\t\t\treturn fmt.Errorf(\"invalid namespace '%s': %v\", ns, err)\n\t\t}\n\n\t\tobjLabels, err := parseLabels()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tobjLabels[tenantLabel] = tenant\n\n\t\tnamespace := corev1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:   ns,\n\t\t\t\tLabels: objLabels,\n\t\t\t},\n\t\t}\n\t\tnamespaces = append(namespaces, namespace)\n\n\t\taccountName := tenant\n\t\tif tenantArgs.account != \"\" {\n\t\t\taccountName = tenantArgs.account\n\t\t}\n\t\tif err := validation.IsQualifiedName(accountName); len(err) > 0 {\n\t\t\treturn fmt.Errorf(\"invalid service-account name '%s': %v\", accountName, err)\n\t\t}\n\n\t\taccount := corev1.ServiceAccount{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      accountName,\n\t\t\t\tNamespace: ns,\n\t\t\t\tLabels:    objLabels,\n\t\t\t},\n\t\t}\n\n\t\taccounts = append(accounts, account)\n\n\t\troleBinding := rbacv1.RoleBinding{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      fmt.Sprintf(\"%s-reconciler\", tenant),\n\t\t\t\tNamespace: ns,\n\t\t\t\tLabels:    objLabels,\n\t\t\t},\n\t\t\tSubjects: []rbacv1.Subject{\n\t\t\t\t{\n\t\t\t\t\tAPIGroup: \"rbac.authorization.k8s.io\",\n\t\t\t\t\tKind:     \"User\",\n\t\t\t\t\tName:     fmt.Sprintf(\"gotk:%s:reconciler\", ns),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKind:      \"ServiceAccount\",\n\t\t\t\t\tName:      accountName,\n\t\t\t\t\tNamespace: ns,\n\t\t\t\t},\n\t\t\t},\n\t\t\tRoleRef: rbacv1.RoleRef{\n\t\t\t\tAPIGroup: \"rbac.authorization.k8s.io\",\n\t\t\t\tKind:     \"ClusterRole\",\n\t\t\t\tName:     tenantArgs.clusterRole,\n\t\t\t},\n\t\t}\n\t\troleBindings = append(roleBindings, roleBinding)\n\t}\n\n\tif createArgs.export {\n\t\tfor i := range tenantArgs.namespaces {\n\t\t\tif err := exportTenant(namespaces[i], accounts[i], roleBindings[i], tenantArgs.skipNamespace); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor i := range tenantArgs.namespaces {\n\t\tif !tenantArgs.skipNamespace {\n\t\t\tlogger.Actionf(\"applying namespace %s\", namespaces[i].Name)\n\t\t\tif err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tlogger.Actionf(\"applying service account %s\", accounts[i].Name)\n\t\tif err := upsertServiceAccount(ctx, kubeClient, accounts[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tlogger.Actionf(\"applying role binding %s\", roleBindings[i].Name)\n\t\tif err := upsertRoleBinding(ctx, kubeClient, roleBindings[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tlogger.Successf(\"tenant setup completed\")\n\treturn nil\n}\n\nfunc upsertNamespace(ctx context.Context, kubeClient client.Client, namespace corev1.Namespace) error {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: namespace.GetNamespace(),\n\t\tName:      namespace.GetName(),\n\t}\n\n\tvar existing corev1.Namespace\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, &namespace); err != nil {\n\t\t\t\treturn err\n\t\t\t} else {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn err\n\t}\n\n\tif !equality.Semantic.DeepDerivative(namespace.Labels, existing.Labels) {\n\t\texisting.Labels = namespace.Labels\n\t\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc upsertServiceAccount(ctx context.Context, kubeClient client.Client, account corev1.ServiceAccount) error {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: account.GetNamespace(),\n\t\tName:      account.GetName(),\n\t}\n\n\tvar existing corev1.ServiceAccount\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, &account); err != nil {\n\t\t\t\treturn err\n\t\t\t} else {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn err\n\t}\n\n\tif !equality.Semantic.DeepDerivative(account.Labels, existing.Labels) {\n\t\texisting.Labels = account.Labels\n\t\tif err := kubeClient.Update(ctx, &existing); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc upsertRoleBinding(ctx context.Context, kubeClient client.Client, roleBinding rbacv1.RoleBinding) error {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: roleBinding.GetNamespace(),\n\t\tName:      roleBinding.GetName(),\n\t}\n\n\tvar existing rbacv1.RoleBinding\n\terr := kubeClient.Get(ctx, namespacedName, &existing)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tif err := kubeClient.Create(ctx, &roleBinding); err != nil {\n\t\t\t\treturn err\n\t\t\t} else {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn err\n\t}\n\n\tif !equality.Semantic.DeepDerivative(roleBinding.Subjects, existing.Subjects) ||\n\t\t!equality.Semantic.DeepDerivative(roleBinding.RoleRef, existing.RoleRef) ||\n\t\t!equality.Semantic.DeepDerivative(roleBinding.Labels, existing.Labels) {\n\t\tif err := kubeClient.Delete(ctx, &existing); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := kubeClient.Create(ctx, &roleBinding); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, roleBinding rbacv1.RoleBinding, skipNamespace bool) error {\n\tvar data []byte\n\tvar err error\n\n\tif !skipNamespace {\n\t\tnamespace.TypeMeta = metav1.TypeMeta{\n\t\t\tAPIVersion: \"v1\",\n\t\t\tKind:       \"Namespace\",\n\t\t}\n\t\tdata, err = yaml.Marshal(namespace)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdata = bytes.Replace(data, []byte(\"spec: {}\\n\"), []byte(\"\"), 1)\n\n\t\tprintlnStdout(\"---\")\n\t\tprintlnStdout(resourceToString(data))\n\t}\n\n\taccount.TypeMeta = metav1.TypeMeta{\n\t\tAPIVersion: \"v1\",\n\t\tKind:       \"ServiceAccount\",\n\t}\n\tdata, err = yaml.Marshal(account)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdata = bytes.Replace(data, []byte(\"spec: {}\\n\"), []byte(\"\"), 1)\n\n\tprintlnStdout(\"---\")\n\tprintlnStdout(resourceToString(data))\n\n\troleBinding.TypeMeta = metav1.TypeMeta{\n\t\tAPIVersion: \"rbac.authorization.k8s.io/v1\",\n\t\tKind:       \"RoleBinding\",\n\t}\n\tdata, err = yaml.Marshal(roleBinding)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprintlnStdout(\"---\")\n\tprintlnStdout(resourceToString(data))\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/create_tenant_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCreateTenant(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\tname:   \"no args\",\n\t\t\targs:   \"create tenant\",\n\t\t\tassert: assertError(\"name is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"no namespace\",\n\t\t\targs:   \"create tenant dev-team --cluster-role=cluster-admin\",\n\t\t\tassert: assertError(\"with-namespace is required\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"basic tenant\",\n\t\t\targs:   \"create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --export\",\n\t\t\tassert: assertGoldenFile(\"./testdata/create_tenant/tenant-basic.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"tenant with custom serviceaccount\",\n\t\t\targs:   \"create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --with-service-account=flux-tenant --export\",\n\t\t\tassert: assertGoldenFile(\"./testdata/create_tenant/tenant-with-service-account.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"tenant with custom cluster role\",\n\t\t\targs:   \"create tenant dev-team --with-namespace=apps --cluster-role=custom-role --export\",\n\t\t\tassert: assertGoldenFile(\"./testdata/create_tenant/tenant-with-cluster-role.yaml\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"tenant with skip namespace\",\n\t\t\targs:   \"create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --skip-namespace --export\",\n\t\t\tassert: assertGoldenFile(\"./testdata/create_tenant/tenant-with-skip-namespace.yaml\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/create_test.go",
    "content": "package main\n\nimport (\n\t\"testing\"\n\n\t\"k8s.io/apimachinery/pkg/util/rand\"\n)\n\nfunc Test_validateObjectName(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tvalid bool\n\t}{\n\t\t{\n\t\t\tname:  \"flux-system\",\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"-flux-system\",\n\t\t\tvalid: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"-flux-system-\",\n\t\t\tvalid: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"third.first\",\n\t\t\tvalid: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"THirdfirst\",\n\t\t\tvalid: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"THirdfirst\",\n\t\t\tvalid: false,\n\t\t},\n\t\t{\n\t\t\tname:  rand.String(63),\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  rand.String(64),\n\t\t\tvalid: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tvalid := validateObjectName(tt.name)\n\t\tif valid != tt.valid {\n\t\t\tt.Errorf(\"expected name %q to return %t for validateObjectName func but got %t\",\n\t\t\t\ttt.name, tt.valid, valid)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/debug.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar debugCmd = &cobra.Command{\n\tUse:   \"debug\",\n\tShort: \"Debug a flux resource\",\n\tLong:  `The debug command can be used to troubleshoot failing resource reconciliations.`,\n}\n\nfunc init() {\n\trootCmd.AddCommand(debugCmd)\n}\n"
  },
  {
    "path": "cmd/flux/debug_helmrelease.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\t\"github.com/fluxcd/pkg/chartutil\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar debugHelmReleaseCmd = &cobra.Command{\n\tUse:     \"helmrelease [name]\",\n\tAliases: []string{\"hr\"},\n\tShort:   \"Debug a HelmRelease resource\",\n\tLong: withPreviewNote(`The debug helmrelease command can be used to troubleshoot failing Helm release reconciliations.\nWARNING: This command will print sensitive information if Kubernetes Secrets are referenced in the HelmRelease .spec.valuesFrom field.`),\n\tExample: `  # Print the status of a Helm release\n  flux debug hr podinfo --show-status\n\n  # Export the final values of a Helm release composed from referred ConfigMaps and Secrets\n  flux debug hr podinfo --show-values > values.yaml\n\n  # Print the reconciliation history of a Helm release\n  flux debug hr podinfo --show-history`,\n\tRunE:              debugHelmReleaseCmdRun,\n\tArgs:              cobra.ExactArgs(1),\n\tValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),\n}\n\ntype debugHelmReleaseFlags struct {\n\tshowStatus  bool\n\tshowValues  bool\n\tshowHistory bool\n}\n\nvar debugHelmReleaseArgs debugHelmReleaseFlags\n\nfunc init() {\n\tdebugHelmReleaseCmd.Flags().BoolVar(&debugHelmReleaseArgs.showStatus, \"show-status\", false, \"print the status of the Helm release\")\n\tdebugHelmReleaseCmd.Flags().BoolVar(&debugHelmReleaseArgs.showValues, \"show-values\", false, \"print the final values of the Helm release\")\n\tdebugHelmReleaseCmd.Flags().BoolVar(&debugHelmReleaseArgs.showHistory, \"show-history\", false, \"print the reconciliation history of the Helm release\")\n\tdebugCmd.AddCommand(debugHelmReleaseCmd)\n}\n\nfunc debugHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tflagsSet := 0\n\tif debugHelmReleaseArgs.showStatus {\n\t\tflagsSet++\n\t}\n\tif debugHelmReleaseArgs.showValues {\n\t\tflagsSet++\n\t}\n\tif debugHelmReleaseArgs.showHistory {\n\t\tflagsSet++\n\t}\n\tif flagsSet != 1 {\n\t\treturn fmt.Errorf(\"exactly one of --show-status, --show-values, or --show-history must be set\")\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\thr := &helmv2.HelmRelease{}\n\thrName := types.NamespacedName{Namespace: *kubeconfigArgs.Namespace, Name: name}\n\tif err := kubeClient.Get(ctx, hrName, hr); err != nil {\n\t\treturn err\n\t}\n\n\tif debugHelmReleaseArgs.showStatus {\n\t\tstatus, err := yaml.Marshal(hr.Status)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trootCmd.Println(\"# Status documentation: https://fluxcd.io/flux/components/helm/helmreleases/#helmrelease-status\")\n\t\trootCmd.Print(string(status))\n\t\treturn nil\n\t}\n\n\tif debugHelmReleaseArgs.showValues {\n\t\tfinalValues, err := chartutil.ChartValuesFromReferences(ctx,\n\t\t\tlogr.Discard(),\n\t\t\tkubeClient,\n\t\t\thr.GetNamespace(),\n\t\t\thr.GetValues(),\n\t\t\thr.Spec.ValuesFrom...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvalues, err := yaml.Marshal(finalValues)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trootCmd.Print(string(values))\n\t}\n\n\tif debugHelmReleaseArgs.showHistory {\n\t\tif len(hr.Status.History) == 0 {\n\t\t\thr.Status.History = helmv2.Snapshots{}\n\t\t}\n\n\t\thistory, err := yaml.Marshal(hr.Status.History)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trootCmd.Println(\"# History documentation: https://fluxcd.io/flux/components/helm/helmreleases/#history\")\n\t\trootCmd.Print(string(history))\n\t\treturn nil\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/debug_helmrelease_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestDebugHelmRelease(t *testing.T) {\n\tnamespace := allocateNamespace(\"debug\")\n\n\tobjectFile := \"testdata/debug_helmrelease/objects.yaml\"\n\ttmpl := map[string]string{\n\t\t\"fluxns\": namespace,\n\t}\n\ttestEnv.CreateObjectFile(objectFile, tmpl, t)\n\n\tcases := []struct {\n\t\tname       string\n\t\targ        string\n\t\tgoldenFile string\n\t\ttmpl       map[string]string\n\t}{\n\t\t{\n\t\t\t\"debug status\",\n\t\t\t\"debug helmrelease test-values-inline --show-status --show-values=false\",\n\t\t\t\"testdata/debug_helmrelease/status.golden.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"debug values\",\n\t\t\t\"debug helmrelease test-values-inline --show-values --show-status=false\",\n\t\t\t\"testdata/debug_helmrelease/values-inline.golden.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"debug values from\",\n\t\t\t\"debug helmrelease test-values-from --show-values --show-status=false\",\n\t\t\t\"testdata/debug_helmrelease/values-from.golden.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"debug history\",\n\t\t\t\"debug helmrelease test-with-history --show-history --show-status=false\",\n\t\t\t\"testdata/debug_helmrelease/history.golden.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"debug history empty\",\n\t\t\t\"debug helmrelease test-values-inline --show-history --show-status=false\",\n\t\t\t\"testdata/debug_helmrelease/history-empty.golden.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t}\n\n\tfor _, tt := range cases {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.arg + \" -n=\" + namespace,\n\t\t\t\tassert: assertGoldenTemplateFile(tt.goldenFile, tmpl),\n\t\t\t}\n\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/debug_kustomization.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\t\"github.com/fluxcd/pkg/kustomize\"\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar debugKustomizationCmd = &cobra.Command{\n\tUse:     \"kustomization [name]\",\n\tAliases: []string{\"ks\"},\n\tShort:   \"Debug a Flux Kustomization resource\",\n\tLong: withPreviewNote(`The debug kustomization command can be used to troubleshoot failing Flux Kustomization reconciliations.\nWARNING: This command will print sensitive information if Kubernetes Secrets are referenced in the Kustomization .spec.postBuild.substituteFrom field.`),\n\tExample: `  # Print the status of a Flux Kustomization\n  flux debug ks podinfo --show-status\n\n  # Export the final variables used for post-build substitutions composed from referred ConfigMaps and Secrets\n  flux debug ks podinfo --show-vars > vars.env\n\n  # Print the reconciliation history of a Flux Kustomization\n  flux debug ks podinfo --show-history`,\n\tRunE:              debugKustomizationCmdRun,\n\tArgs:              cobra.ExactArgs(1),\n\tValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),\n}\n\ntype debugKustomizationFlags struct {\n\tshowStatus  bool\n\tshowVars    bool\n\tshowHistory bool\n}\n\nvar debugKustomizationArgs debugKustomizationFlags\n\nfunc init() {\n\tdebugKustomizationCmd.Flags().BoolVar(&debugKustomizationArgs.showStatus, \"show-status\", false, \"print the status of the Flux Kustomization\")\n\tdebugKustomizationCmd.Flags().BoolVar(&debugKustomizationArgs.showVars, \"show-vars\", false, \"print the final vars of the Flux Kustomization in dot env format\")\n\tdebugKustomizationCmd.Flags().BoolVar(&debugKustomizationArgs.showHistory, \"show-history\", false, \"print the reconciliation history of the Flux Kustomization\")\n\tdebugCmd.AddCommand(debugKustomizationCmd)\n}\n\nfunc debugKustomizationCmdRun(cmd *cobra.Command, args []string) error {\n\tname := args[0]\n\n\tflagsSet := 0\n\tif debugKustomizationArgs.showStatus {\n\t\tflagsSet++\n\t}\n\tif debugKustomizationArgs.showVars {\n\t\tflagsSet++\n\t}\n\tif debugKustomizationArgs.showHistory {\n\t\tflagsSet++\n\t}\n\tif flagsSet != 1 {\n\t\treturn fmt.Errorf(\"exactly one of --show-status, --show-vars, or --show-history must be set\")\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tks := &kustomizev1.Kustomization{}\n\tksName := types.NamespacedName{Namespace: *kubeconfigArgs.Namespace, Name: name}\n\tif err := kubeClient.Get(ctx, ksName, ks); err != nil {\n\t\treturn err\n\t}\n\n\tif debugKustomizationArgs.showStatus {\n\t\tstatus, err := yaml.Marshal(ks.Status)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trootCmd.Println(\"# Status documentation: https://fluxcd.io/flux/components/kustomize/kustomizations/#kustomization-status\")\n\t\trootCmd.Print(string(status))\n\t\treturn nil\n\t}\n\n\tif debugKustomizationArgs.showVars {\n\t\tif ks.Spec.PostBuild == nil {\n\t\t\treturn errors.New(\"no post build substitutions found\")\n\t\t}\n\n\t\tksObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ks)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfinalVars, err := kustomize.LoadVariables(ctx, kubeClient, unstructured.Unstructured{Object: ksObj})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif len(ks.Spec.PostBuild.Substitute) > 0 {\n\t\t\tfor k, v := range ks.Spec.PostBuild.Substitute {\n\t\t\t\t// Remove new lines from the values as they are not supported.\n\t\t\t\t// Replicates the controller behavior from\n\t\t\t\t// https://github.com/fluxcd/pkg/blob/main/kustomize/kustomize_varsub.go\n\t\t\t\tfinalVars[k] = strings.ReplaceAll(v, \"\\n\", \"\")\n\t\t\t}\n\t\t}\n\n\t\tkeys := make([]string, 0, len(finalVars))\n\t\tfor k := range finalVars {\n\t\t\tkeys = append(keys, k)\n\t\t}\n\t\tsort.Strings(keys)\n\n\t\tfor _, k := range keys {\n\t\t\trootCmd.Println(k + \"=\" + finalVars[k])\n\t\t}\n\t}\n\n\tif debugKustomizationArgs.showHistory {\n\t\tif len(ks.Status.History) == 0 {\n\t\t\tks.Status.History = meta.History{}\n\t\t}\n\n\t\thistory, err := yaml.Marshal(ks.Status.History)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trootCmd.Println(\"# History documentation: https://fluxcd.io/flux/components/kustomize/kustomizations/#history\")\n\t\trootCmd.Print(string(history))\n\t\treturn nil\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/debug_kustomization_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestDebugKustomization(t *testing.T) {\n\tnamespace := allocateNamespace(\"debug\")\n\n\tobjectFile := \"testdata/debug_kustomization/objects.yaml\"\n\ttmpl := map[string]string{\n\t\t\"fluxns\": namespace,\n\t}\n\ttestEnv.CreateObjectFile(objectFile, tmpl, t)\n\n\tcases := []struct {\n\t\tname       string\n\t\targ        string\n\t\tgoldenFile string\n\t\ttmpl       map[string]string\n\t}{\n\t\t{\n\t\t\t\"debug status\",\n\t\t\t\"debug ks test --show-status --show-vars=false\",\n\t\t\t\"testdata/debug_kustomization/status.golden.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"debug vars\",\n\t\t\t\"debug ks test --show-vars --show-status=false\",\n\t\t\t\"testdata/debug_kustomization/vars.golden.env\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"debug vars from\",\n\t\t\t\"debug ks test-from --show-vars --show-status=false\",\n\t\t\t\"testdata/debug_kustomization/vars-from.golden.env\",\n\t\t\ttmpl,\n\t\t}, {\n\t\t\t\"debug history\",\n\t\t\t\"debug ks test-with-history --show-history --show-status=false\",\n\t\t\t\"testdata/debug_kustomization/history.golden.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"debug history empty\",\n\t\t\t\"debug ks test --show-history --show-status=false\",\n\t\t\t\"testdata/debug_kustomization/history-empty.golden.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t}\n\n\tfor _, tt := range cases {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.arg + \" -n=\" + namespace,\n\t\t\t\tassert: assertGoldenTemplateFile(tt.goldenFile, tmpl),\n\t\t\t}\n\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/delete.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/manifoldco/promptui\"\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar deleteCmd = &cobra.Command{\n\tUse:   \"delete\",\n\tShort: \"Delete sources and resources\",\n\tLong:  `The delete sub-commands delete sources and resources.`,\n}\n\ntype deleteFlags struct {\n\tsilent bool\n}\n\nvar deleteArgs deleteFlags\n\nfunc init() {\n\tdeleteCmd.PersistentFlags().BoolVarP(&deleteArgs.silent, \"silent\", \"s\", false,\n\t\t\"delete resource without asking for confirmation\")\n\n\trootCmd.AddCommand(deleteCmd)\n}\n\ntype deleteCommand struct {\n\tapiType\n\tobject adapter // for getting the value, and later deleting it\n}\n\nfunc (del deleteCommand) run(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"%s name is required\", del.humanKind)\n\t}\n\tname := args[0]\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\tName:      name,\n\t}\n\n\terr = kubeClient.Get(ctx, namespacedName, del.object.asClientObject())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !deleteArgs.silent {\n\t\tprompt := promptui.Prompt{\n\t\t\tLabel:     \"Are you sure you want to delete this \" + del.humanKind,\n\t\t\tIsConfirm: true,\n\t\t}\n\t\tif _, err := prompt.Run(); err != nil {\n\t\t\treturn fmt.Errorf(\"aborting\")\n\t\t}\n\t}\n\n\tlogger.Actionf(\"deleting %s %s in %s namespace\", del.humanKind, name, *kubeconfigArgs.Namespace)\n\terr = kubeClient.Delete(ctx, del.object.asClientObject())\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"%s deleted\", del.humanKind)\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/delete_alert.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\nvar deleteAlertCmd = &cobra.Command{\n\tUse:   \"alert [name]\",\n\tShort: \"Delete a Alert resource\",\n\tLong:  withPreviewNote(\"The delete alert command removes the given Alert from the cluster.\"),\n\tExample: `  # Delete an Alert and the Kubernetes resources created by it\n  flux delete alert main`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),\n\tRunE: deleteCommand{\n\t\tapiType: alertType,\n\t\tobject:  universalAdapter{&notificationv1.Alert{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteCmd.AddCommand(deleteAlertCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_alertprovider.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\nvar deleteAlertProviderCmd = &cobra.Command{\n\tUse:   \"alert-provider [name]\",\n\tShort: \"Delete a Provider resource\",\n\tLong:  withPreviewNote(\"The delete alert-provider command removes the given Provider from the cluster.\"),\n\tExample: `  # Delete a Provider and the Kubernetes resources created by it\n  flux delete alert-provider slack`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),\n\tRunE: deleteCommand{\n\t\tapiType: alertProviderType,\n\t\tobject:  universalAdapter{&notificationv1.Provider{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteCmd.AddCommand(deleteAlertProviderCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_helmrelease.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n)\n\nvar deleteHelmReleaseCmd = &cobra.Command{\n\tUse:     \"helmrelease [name]\",\n\tAliases: []string{\"hr\"},\n\tShort:   \"Delete a HelmRelease resource\",\n\tLong:    \"The delete helmrelease command removes the given HelmRelease from the cluster.\",\n\tExample: `  # Delete a Helm release and the Kubernetes resources created by it\n  flux delete hr podinfo`,\n\tValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),\n\tRunE: deleteCommand{\n\t\tapiType: helmReleaseType,\n\t\tobject:  universalAdapter{&helmv2.HelmRelease{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteCmd.AddCommand(deleteHelmReleaseCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_image.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar deleteImageCmd = &cobra.Command{\n\tUse:   \"image\",\n\tShort: \"Delete image automation objects\",\n\tLong:  `The delete image sub-commands delete image automation objects.`,\n}\n\nfunc init() {\n\tdeleteCmd.AddCommand(deleteImageCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_image_policy.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar deleteImagePolicyCmd = &cobra.Command{\n\tUse:   \"policy [name]\",\n\tShort: \"Delete an ImagePolicy object\",\n\tLong:  `The delete image policy command deletes the given ImagePolicy from the cluster.`,\n\tExample: `  # Delete an image policy\n  flux delete image policy alpine3.x`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),\n\tRunE: deleteCommand{\n\t\tapiType: imagePolicyType,\n\t\tobject:  universalAdapter{&imagev1.ImagePolicy{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteImageCmd.AddCommand(deleteImagePolicyCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_image_repository.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar deleteImageRepositoryCmd = &cobra.Command{\n\tUse:   \"repository [name]\",\n\tShort: \"Delete an ImageRepository object\",\n\tLong:  \"The delete image repository command deletes the given ImageRepository from the cluster.\",\n\tExample: `  # Delete an image repository\n  flux delete image repository alpine`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),\n\tRunE: deleteCommand{\n\t\tapiType: imageRepositoryType,\n\t\tobject:  universalAdapter{&imagev1.ImageRepository{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteImageCmd.AddCommand(deleteImageRepositoryCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_image_update.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n)\n\nvar deleteImageUpdateCmd = &cobra.Command{\n\tUse:   \"update [name]\",\n\tShort: \"Delete an ImageUpdateAutomation object\",\n\tLong:  `The delete image update command deletes the given ImageUpdateAutomation from the cluster.`,\n\tExample: `  # Delete an image update automation\n  flux delete image update latest-images`,\n\tValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),\n\tRunE: deleteCommand{\n\t\tapiType: imageUpdateAutomationType,\n\t\tobject:  universalAdapter{&autov1.ImageUpdateAutomation{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteImageCmd.AddCommand(deleteImageUpdateCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_kustomization.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n)\n\nvar deleteKsCmd = &cobra.Command{\n\tUse:     \"kustomization [name]\",\n\tAliases: []string{\"ks\"},\n\tShort:   \"Delete a Kustomization resource\",\n\tLong:    `The delete kustomization command deletes the given Kustomization from the cluster.`,\n\tExample: `  # Delete a kustomization and the Kubernetes resources created by it when prune is enabled\n  flux delete kustomization podinfo`,\n\tValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),\n\tRunE: deleteCommand{\n\t\tapiType: kustomizationType,\n\t\tobject:  universalAdapter{&kustomizev1.Kustomization{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteCmd.AddCommand(deleteKsCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_receiver.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n)\n\nvar deleteReceiverCmd = &cobra.Command{\n\tUse:   \"receiver [name]\",\n\tShort: \"Delete a Receiver resource\",\n\tLong:  `The delete receiver command removes the given Receiver from the cluster.`,\n\tExample: `  # Delete an Receiver and the Kubernetes resources created by it\n  flux delete receiver main`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),\n\tRunE: deleteCommand{\n\t\tapiType: receiverType,\n\t\tobject:  universalAdapter{&notificationv1.Receiver{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteCmd.AddCommand(deleteReceiverCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_source.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar deleteSourceCmd = &cobra.Command{\n\tUse:   \"source\",\n\tShort: \"Delete sources\",\n\tLong:  `The delete source sub-commands delete sources.`,\n}\n\nfunc init() {\n\tdeleteCmd.AddCommand(deleteSourceCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_source_bucket.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar deleteSourceBucketCmd = &cobra.Command{\n\tUse:   \"bucket [name]\",\n\tShort: \"Delete a Bucket source\",\n\tLong:  \"The delete source bucket command deletes the given Bucket from the cluster.\",\n\tExample: `  # Delete a Bucket source\n  flux delete source bucket podinfo`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),\n\tRunE: deleteCommand{\n\t\tapiType: bucketType,\n\t\tobject:  universalAdapter{&sourcev1.Bucket{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteSourceCmd.AddCommand(deleteSourceBucketCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_source_chart.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar deleteSourceChartCmd = &cobra.Command{\n\tUse:   \"chart [name]\",\n\tShort: \"Delete a HelmChart source\",\n\tLong:  \"The delete source chart command deletes the given HelmChart from the cluster.\",\n\tExample: `  # Delete a HelmChart\n  flux delete source chart podinfo`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),\n\tRunE: deleteCommand{\n\t\tapiType: helmChartType,\n\t\tobject:  universalAdapter{&sourcev1.HelmChart{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteSourceCmd.AddCommand(deleteSourceChartCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_source_git.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar deleteSourceGitCmd = &cobra.Command{\n\tUse:   \"git [name]\",\n\tShort: \"Delete a GitRepository source\",\n\tLong:  `The delete source git command deletes the given GitRepository from the cluster.`,\n\tExample: `  # Delete a Git repository\n  flux delete source git podinfo`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),\n\tRunE: deleteCommand{\n\t\tapiType: gitRepositoryType,\n\t\tobject:  universalAdapter{&sourcev1.GitRepository{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteSourceCmd.AddCommand(deleteSourceGitCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_source_helm.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar deleteSourceHelmCmd = &cobra.Command{\n\tUse:   \"helm [name]\",\n\tShort: \"Delete a HelmRepository source\",\n\tLong:  \"The delete source helm command deletes the given HelmRepository from the cluster.\",\n\tExample: `  # Delete a Helm repository\n  flux delete source helm podinfo`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),\n\tRunE: deleteCommand{\n\t\tapiType: helmRepositoryType,\n\t\tobject:  universalAdapter{&sourcev1.HelmRepository{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteSourceCmd.AddCommand(deleteSourceHelmCmd)\n}\n"
  },
  {
    "path": "cmd/flux/delete_source_oci.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar deleteSourceOCIRepositoryCmd = &cobra.Command{\n\tUse:   \"oci [name]\",\n\tShort: \"Delete an OCIRepository source\",\n\tLong:  withPreviewNote(\"The delete source oci command deletes the given OCIRepository from the cluster.\"),\n\tExample: `  # Delete an OCIRepository \n  flux delete source oci podinfo`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),\n\tRunE: deleteCommand{\n\t\tapiType: ociRepositoryType,\n\t\tobject:  universalAdapter{&sourcev1.OCIRepository{}},\n\t}.run,\n}\n\nfunc init() {\n\tdeleteSourceCmd.AddCommand(deleteSourceOCIRepositoryCmd)\n}\n"
  },
  {
    "path": "cmd/flux/diff.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar diffCmd = &cobra.Command{\n\tUse:   \"diff\",\n\tShort: \"Diff a flux resource\",\n\tLong:  `The diff command is used to do a server-side dry-run on flux resources, then prints the diff.`,\n}\n\nfunc init() {\n\trootCmd.AddCommand(diffCmd)\n}\n"
  },
  {
    "path": "cmd/flux/diff_artifact.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fluxcd/pkg/oci\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\t\"github.com/google/go-containerregistry/pkg/crane\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n)\n\nvar diffArtifactCmd = &cobra.Command{\n\tUse:   \"artifact\",\n\tShort: \"Diff Artifact\",\n\tLong:  `The diff artifact command computes the diff between the remote OCI artifact and a local directory or file`,\n\tExample: `# Check if local files differ from remote\nflux diff artifact oci://ghcr.io/stefanprodan/manifests:podinfo:6.2.0 --path=./kustomize`,\n\tRunE: diffArtifactCmdRun,\n}\n\ntype diffArtifactFlags struct {\n\tpath        string\n\tcreds       string\n\tprovider    flags.SourceOCIProvider\n\tignorePaths []string\n\tinsecure    bool\n}\n\nvar diffArtifactArgs = newDiffArtifactArgs()\n\nfunc newDiffArtifactArgs() diffArtifactFlags {\n\treturn diffArtifactFlags{\n\t\tprovider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),\n\t}\n}\n\nfunc init() {\n\tdiffArtifactCmd.Flags().StringVar(&diffArtifactArgs.path, \"path\", \"\", \"path to the directory where the Kubernetes manifests are located\")\n\tdiffArtifactCmd.Flags().StringVar(&diffArtifactArgs.creds, \"creds\", \"\", \"credentials for OCI registry in the format <username>[:<password>] if --provider is generic\")\n\tdiffArtifactCmd.Flags().Var(&diffArtifactArgs.provider, \"provider\", sourceOCIRepositoryArgs.provider.Description())\n\tdiffArtifactCmd.Flags().StringSliceVar(&diffArtifactArgs.ignorePaths, \"ignore-paths\", excludeOCI, \"set paths to ignore in .gitignore format\")\n\tdiffArtifactCmd.Flags().BoolVar(&diffArtifactArgs.insecure, \"insecure-registry\", false, \"allows the remote artifact to be pulled without TLS\")\n\tdiffCmd.AddCommand(diffArtifactCmd)\n}\n\nfunc diffArtifactCmdRun(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"artifact URL is required\")\n\t}\n\tociURL := args[0]\n\n\tif diffArtifactArgs.path == \"\" {\n\t\treturn fmt.Errorf(\"invalid path %q\", diffArtifactArgs.path)\n\t}\n\n\turl, err := oci.ParseArtifactURL(ociURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := os.Stat(diffArtifactArgs.path); err != nil {\n\t\treturn fmt.Errorf(\"invalid path '%s', must point to an existing directory or file\", diffArtifactArgs.path)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\topts := oci.DefaultOptions()\n\n\tif diffArtifactArgs.insecure {\n\t\topts = append(opts, crane.Insecure)\n\t}\n\n\tif diffArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {\n\t\tlogger.Actionf(\"logging in to registry with provider credentials\")\n\t\topt, _, err := loginWithProvider(ctx, url, diffArtifactArgs.provider.String())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error during login with provider: %w\", err)\n\t\t}\n\t\topts = append(opts, opt)\n\t}\n\n\tociClient := oci.NewClient(opts)\n\n\tif diffArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && diffArtifactArgs.creds != \"\" {\n\t\tlogger.Actionf(\"logging in to registry with credentials\")\n\t\tif err := ociClient.LoginWithCredentials(diffArtifactArgs.creds); err != nil {\n\t\t\treturn fmt.Errorf(\"could not login with credentials: %w\", err)\n\t\t}\n\t}\n\n\tif err := ociClient.Diff(ctx, url, diffArtifactArgs.path, diffArtifactArgs.ignorePaths); err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Successf(\"no changes detected\")\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/diff_artifact_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/distribution/distribution/v3/configuration\"\n\t\"github.com/distribution/distribution/v3/registry\"\n\t_ \"github.com/distribution/distribution/v3/registry/auth/htpasswd\"\n\t_ \"github.com/distribution/distribution/v3/registry/storage/driver/inmemory\"\n\t\"github.com/phayes/freeport\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n)\n\nvar dockerReg string\n\nfunc setupRegistryServer(ctx context.Context) error {\n\t// Registry config\n\tconfig := &configuration.Configuration{}\n\tport, err := freeport.GetFreePort()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get free port: %s\", err)\n\t}\n\n\tdockerReg = fmt.Sprintf(\"localhost:%d\", port)\n\tconfig.HTTP.Addr = fmt.Sprintf(\"127.0.0.1:%d\", port)\n\tconfig.HTTP.DrainTimeout = time.Duration(10) * time.Second\n\tconfig.Storage = map[string]configuration.Parameters{\"inmemory\": map[string]interface{}{}}\n\tdockerRegistry, err := registry.NewRegistry(ctx, config)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create docker registry: %w\", err)\n\t}\n\n\t// Start Docker registry\n\tgo dockerRegistry.ListenAndServe()\n\n\treturn nil\n}\n\nfunc TestDiffArtifact(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\turl      string\n\t\targsTpl  string\n\t\tpushFile string\n\t\tdiffFile string\n\t\tassert   assertFunc\n\t}{\n\t\t{\n\t\t\tname:     \"should not fail if there is no diff\",\n\t\t\turl:      \"oci://%s/podinfo:1.0.0\",\n\t\t\targsTpl:  \"diff artifact  %s --path=%s\",\n\t\t\tpushFile: \"./testdata/diff-artifact/deployment.yaml\",\n\t\t\tdiffFile: \"./testdata/diff-artifact/deployment.yaml\",\n\t\t\tassert:   assertGoldenFile(\"testdata/diff-artifact/success.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"should fail if there is a diff\",\n\t\t\turl:      \"oci://%s/podinfo:2.0.0\",\n\t\t\targsTpl:  \"diff artifact %s --path=%s\",\n\t\t\tpushFile: \"./testdata/diff-artifact/deployment.yaml\",\n\t\t\tdiffFile: \"./testdata/diff-artifact/deployment-diff.yaml\",\n\t\t\tassert:   assertError(\"the remote artifact contents differs from the local one\"),\n\t\t},\n\t}\n\n\tctx := ctrl.SetupSignalHandler()\n\terr := setupRegistryServer(ctx)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"failed to start docker registry: %s\", err))\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.url = fmt.Sprintf(tt.url, dockerReg)\n\t\t\t_, err := executeCommand(\"push artifact \" + tt.url + \" --path=\" + tt.pushFile + \" --source=test --revision=test\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(fmt.Errorf(\"failed to push image: %w\", err).Error())\n\t\t\t}\n\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   fmt.Sprintf(tt.argsTpl, tt.url, tt.diffFile),\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/diff_kustomization.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\n\t\"github.com/spf13/cobra\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/build\"\n)\n\nvar diffKsCmd = &cobra.Command{\n\tUse:     \"kustomization\",\n\tAliases: []string{\"ks\"},\n\tShort:   \"Diff Kustomization\",\n\tLong: `The diff command does a build, then it performs a server-side dry-run and prints the diff.\nExit status: 0 No differences were found. 1 Differences were found. >1 diff failed with an error.`,\n\tExample: `# Preview local changes as they were applied on the cluster\nflux diff kustomization my-app --path ./path/to/local/manifests\n\n# Preview using a local flux kustomization file\nflux diff kustomization my-app --path ./path/to/local/manifests \\\n\t--kustomization-file ./path/to/local/my-app.yaml\n\n# Exclude files by providing a comma separated list of entries that follow the .gitignore pattern fromat.\nflux diff kustomization my-app --path ./path/to/local/manifests \\\n\t--kustomization-file ./path/to/local/my-app.yaml \\\n\t--ignore-paths \"/to_ignore/**/*.yaml,ignore.yaml\"\n\n# Run recursively on all encountered Kustomizations\nflux diff kustomization my-app --path ./path/to/local/manifests \\\n    --recursive \\\n    --local-sources GitRepository/flux-system/my-repo=./path/to/local/git`,\n\tValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),\n\tRunE:              diffKsCmdRun,\n}\n\ntype diffKsFlags struct {\n\tkustomizationFile string\n\tpath              string\n\tignorePaths       []string\n\tprogressBar       bool\n\tstrictSubst       bool\n\trecursive         bool\n\tlocalSources      map[string]string\n}\n\nvar diffKsArgs diffKsFlags\n\nfunc init() {\n\tdiffKsCmd.Flags().StringVar(&diffKsArgs.path, \"path\", \"\", \"Path to a local directory that matches the specified Kustomization.spec.path.\")\n\tdiffKsCmd.Flags().BoolVar(&diffKsArgs.progressBar, \"progress-bar\", true, \"Boolean to set the progress bar. The default value is true.\")\n\tdiffKsCmd.Flags().StringSliceVar(&diffKsArgs.ignorePaths, \"ignore-paths\", nil, \"set paths to ignore in .gitignore format\")\n\tdiffKsCmd.Flags().StringVar(&diffKsArgs.kustomizationFile, \"kustomization-file\", \"\", \"Path to the Flux Kustomization YAML file.\")\n\tdiffKsCmd.Flags().BoolVar(&diffKsArgs.strictSubst, \"strict-substitute\", false,\n\t\t\"When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.\")\n\tdiffKsCmd.Flags().BoolVarP(&diffKsArgs.recursive, \"recursive\", \"r\", false, \"Recursively diff Kustomizations\")\n\tdiffKsCmd.Flags().StringToStringVar(&diffKsArgs.localSources, \"local-sources\", nil, \"Comma-separated list of repositories in format: Kind/namespace/name=path\")\n\tdiffCmd.AddCommand(diffKsCmd)\n}\n\nfunc diffKsCmdRun(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"%s name is required\", kustomizationType.humanKind)\n\t}\n\tname := args[0]\n\n\tif diffKsArgs.path == \"\" {\n\t\treturn &RequestError{StatusCode: 2, Err: fmt.Errorf(\"invalid resource path %q\", diffKsArgs.path)}\n\t}\n\n\tif fs, err := os.Stat(diffKsArgs.path); err != nil || !fs.IsDir() {\n\t\treturn &RequestError{StatusCode: 2, Err: fmt.Errorf(\"invalid resource path %q\", diffKsArgs.path)}\n\t}\n\n\tif diffKsArgs.kustomizationFile != \"\" {\n\t\tif fs, err := os.Stat(diffKsArgs.kustomizationFile); os.IsNotExist(err) || fs.IsDir() {\n\t\t\treturn fmt.Errorf(\"invalid kustomization file %q\", diffKsArgs.kustomizationFile)\n\t\t}\n\t}\n\n\tvar (\n\t\tbuilder *build.Builder\n\t\terr     error\n\t)\n\tif diffKsArgs.progressBar {\n\t\tbuilder, err = build.NewBuilder(name, diffKsArgs.path,\n\t\t\tbuild.WithClientConfig(kubeconfigArgs, kubeclientOptions),\n\t\t\tbuild.WithTimeout(rootArgs.timeout),\n\t\t\tbuild.WithKustomizationFile(diffKsArgs.kustomizationFile),\n\t\t\tbuild.WithProgressBar(),\n\t\t\tbuild.WithIgnore(diffKsArgs.ignorePaths),\n\t\t\tbuild.WithStrictSubstitute(diffKsArgs.strictSubst),\n\t\t\tbuild.WithRecursive(diffKsArgs.recursive),\n\t\t\tbuild.WithLocalSources(diffKsArgs.localSources),\n\t\t\tbuild.WithSingleKustomization(),\n\t\t)\n\t} else {\n\t\tbuilder, err = build.NewBuilder(name, diffKsArgs.path,\n\t\t\tbuild.WithClientConfig(kubeconfigArgs, kubeclientOptions),\n\t\t\tbuild.WithTimeout(rootArgs.timeout),\n\t\t\tbuild.WithKustomizationFile(diffKsArgs.kustomizationFile),\n\t\t\tbuild.WithIgnore(diffKsArgs.ignorePaths),\n\t\t\tbuild.WithStrictSubstitute(diffKsArgs.strictSubst),\n\t\t\tbuild.WithRecursive(diffKsArgs.recursive),\n\t\t\tbuild.WithLocalSources(diffKsArgs.localSources),\n\t\t\tbuild.WithSingleKustomization(),\n\t\t)\n\t}\n\n\tif err != nil {\n\t\treturn &RequestError{StatusCode: 2, Err: err}\n\t}\n\n\t// create a signal channel\n\tsigc := make(chan os.Signal, 1)\n\tsignal.Notify(sigc, os.Interrupt)\n\n\terrChan := make(chan error)\n\tgo func() {\n\t\toutput, hasChanged, err := builder.Diff()\n\t\tif err != nil {\n\t\t\terrChan <- &RequestError{StatusCode: 2, Err: err}\n\t\t}\n\n\t\tcmd.Print(output)\n\n\t\tif hasChanged {\n\t\t\terrChan <- &RequestError{StatusCode: 1, Err: fmt.Errorf(\"identified at least one change, exiting with non-zero exit code\")}\n\t\t} else {\n\t\t\terrChan <- nil\n\t\t}\n\t}()\n\n\tselect {\n\tcase <-sigc:\n\t\tif diffKsArgs.progressBar {\n\t\t\terr := builder.StopSpinner()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tfmt.Println(\"Build cancelled... exiting.\")\n\t\treturn builder.Cancel()\n\tcase err := <-errChan:\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n\n}\n"
  },
  {
    "path": "cmd/flux/diff_kustomization_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/build\"\n\t\"github.com/fluxcd/pkg/ssa\"\n\t\"github.com/fluxcd/pkg/ssa/normalize\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\nfunc TestDiffKustomization(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\targs       string\n\t\tobjectFile string\n\t\tassert     assertFunc\n\t}{\n\t\t{\n\t\t\tname:       \"no args\",\n\t\t\targs:       \"diff kustomization podinfo\",\n\t\t\tobjectFile: \"\",\n\t\t\tassert:     assertError(\"invalid resource path \\\"\\\"\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"diff nothing deployed\",\n\t\t\targs:       \"diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false\",\n\t\t\tobjectFile: \"\",\n\t\t\tassert:     assertGoldenFile(\"./testdata/diff-kustomization/nothing-is-deployed.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"diff with a deployment object\",\n\t\t\targs:       \"diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false\",\n\t\t\tobjectFile: \"./testdata/diff-kustomization/deployment.yaml\",\n\t\t\tassert:     assertGoldenFile(\"./testdata/diff-kustomization/diff-with-deployment.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"diff with a drifted service object\",\n\t\t\targs:       \"diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false\",\n\t\t\tobjectFile: \"./testdata/diff-kustomization/service.yaml\",\n\t\t\tassert:     assertGoldenFile(\"./testdata/diff-kustomization/diff-with-drifted-service.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"diff with a drifted secret object\",\n\t\t\targs:       \"diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false\",\n\t\t\tobjectFile: \"./testdata/diff-kustomization/secret.yaml\",\n\t\t\tassert:     assertGoldenFile(\"./testdata/diff-kustomization/diff-with-drifted-secret.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"diff with a drifted key in sops secret object\",\n\t\t\targs:       \"diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false\",\n\t\t\tobjectFile: \"./testdata/diff-kustomization/key-sops-secret.yaml\",\n\t\t\tassert:     assertGoldenFile(\"./testdata/diff-kustomization/diff-with-drifted-key-sops-secret.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"diff with a drifted value in sops secret object\",\n\t\t\targs:       \"diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false\",\n\t\t\tobjectFile: \"./testdata/diff-kustomization/value-sops-secret.yaml\",\n\t\t\tassert:     assertGoldenFile(\"./testdata/diff-kustomization/diff-with-drifted-value-sops-secret.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"diff with a sops dockerconfigjson secret object\",\n\t\t\targs:       \"diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false\",\n\t\t\tobjectFile: \"./testdata/diff-kustomization/dockerconfigjson-sops-secret.yaml\",\n\t\t\tassert:     assertGoldenFile(\"./testdata/diff-kustomization/diff-with-dockerconfigjson-sops-secret.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"diff with a sops stringdata secret object\",\n\t\t\targs:       \"diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false\",\n\t\t\tobjectFile: \"./testdata/diff-kustomization/stringdata-sops-secret.yaml\",\n\t\t\tassert:     assertGoldenFile(\"./testdata/diff-kustomization/diff-with-drifted-stringdata-sops-secret.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"diff where kustomization file has multiple objects with the same name\",\n\t\t\targs:       \"diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false --kustomization-file ./testdata/diff-kustomization/flux-kustomization-multiobj.yaml\",\n\t\t\tobjectFile: \"\",\n\t\t\tassert:     assertGoldenFile(\"./testdata/diff-kustomization/nothing-is-deployed.golden\"),\n\t\t},\n\t\t{\n\t\t\tname:       \"diff with recursive\",\n\t\t\targs:       \"diff kustomization podinfo --path ./testdata/build-kustomization/podinfo-with-my-app --progress-bar=false --recursive --local-sources GitRepository/default/podinfo=./testdata/build-kustomization\",\n\t\t\tobjectFile: \"./testdata/diff-kustomization/my-app.yaml\",\n\t\t\tassert:     assertGoldenFile(\"./testdata/diff-kustomization/diff-with-recursive.golden\"),\n\t\t},\n\t}\n\n\ttmpl := map[string]string{\n\t\t\"fluxns\": allocateNamespace(\"flux-system\"),\n\t}\n\n\tb, _ := build.NewBuilder(\"podinfo\", \"\", build.WithClientConfig(kubeconfigArgs, kubeclientOptions))\n\n\tresourceManager, err := b.Manager()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsetup(t, tmpl)\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif tt.objectFile != \"\" {\n\t\t\t\tif _, err := resourceManager.ApplyAll(context.Background(), createObjectFromFile(tt.objectFile, tmpl, t), ssa.DefaultApplyOptions()); err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args + \" -n \" + tmpl[\"fluxns\"],\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t\tif tt.objectFile != \"\" {\n\t\t\t\ttestEnv.DeleteObjectFile(tt.objectFile, tmpl, t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createObjectFromFile(objectFile string, templateValues map[string]string, t *testing.T) []*unstructured.Unstructured {\n\tbuf, err := os.ReadFile(objectFile)\n\tif err != nil {\n\t\tt.Fatalf(\"Error reading file '%s': %v\", objectFile, err)\n\t}\n\tcontent, err := executeTemplate(string(buf), templateValues)\n\tif err != nil {\n\t\tt.Fatalf(\"Error evaluating template file '%s': '%v'\", objectFile, err)\n\t}\n\tclientObjects, err := readYamlObjects(strings.NewReader(content))\n\tif err != nil {\n\t\tt.Fatalf(\"Error decoding yaml file '%s': %v\", objectFile, err)\n\t}\n\n\tif err := normalize.UnstructuredList(clientObjects); err != nil {\n\t\tt.Fatalf(\"Error setting native kinds defaults for '%s': %v\", objectFile, err)\n\t}\n\n\treturn clientObjects\n}\n"
  },
  {
    "path": "cmd/flux/docgen.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/cobra/doc\"\n)\n\nconst fmTemplate = `---\ntitle: \"%s\"\n---\n`\n\nvar (\n\tcmdDocPath string\n)\n\nvar docgenCmd = &cobra.Command{\n\tUse:    \"docgen\",\n\tShort:  \"Generate the documentation for the CLI commands.\",\n\tHidden: true,\n\tRunE:   docgenCmdRun,\n}\n\nfunc init() {\n\tdocgenCmd.Flags().StringVar(&cmdDocPath, \"path\", \"./docs/cmd\", \"path to write the generated documentation to\")\n\n\trootCmd.AddCommand(docgenCmd)\n}\n\nfunc docgenCmdRun(cmd *cobra.Command, args []string) error {\n\terr := doc.GenMarkdownTreeCustom(rootCmd, cmdDocPath, frontmatterPrepender, linkHandler)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc frontmatterPrepender(filename string) string {\n\tname := filepath.Base(filename)\n\tbase := strings.TrimSuffix(name, path.Ext(name))\n\ttitle := strings.Replace(base, \"_\", \" \", -1)\n\treturn fmt.Sprintf(fmTemplate, title)\n}\n\nfunc linkHandler(name string) string {\n\tbase := strings.TrimSuffix(name, path.Ext(name))\n\treturn \"../\" + strings.ToLower(base) + \"/\"\n}\n"
  },
  {
    "path": "cmd/flux/envsubst.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\n\t\"github.com/fluxcd/pkg/envsubst\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar envsubstCmd = &cobra.Command{\n\tUse:   \"envsubst\",\n\tArgs:  cobra.NoArgs,\n\tShort: \"envsubst substitutes the values of environment variables\",\n\tLong: withPreviewNote(`The envsubst command substitutes the values of environment variables\nin the string piped as standard input and writes the result to the standard output. This command can be used\nto replicate the behavior of the Flux Kustomization post-build substitutions.`),\n\tExample: `  # Run env var substitutions on the kustomization build output\n  export cluster_region=eu-central-1\n  kustomize build . | flux envsubst\n\n  # Run env var substitutions and error out if a variable is not set\n  kustomize build . | flux envsubst --strict\n`,\n\tRunE: runEnvsubstCmd,\n}\n\ntype envsubstFlags struct {\n\tstrict bool\n}\n\nvar envsubstArgs envsubstFlags\n\nfunc init() {\n\tenvsubstCmd.Flags().BoolVar(&envsubstArgs.strict, \"strict\", false,\n\t\t\"fail if a variable without a default value is declared in the input but is missing from the environment\")\n\trootCmd.AddCommand(envsubstCmd)\n}\n\nfunc runEnvsubstCmd(cmd *cobra.Command, args []string) error {\n\tstdin := bufio.NewScanner(rootCmd.InOrStdin())\n\tstdout := bufio.NewWriter(rootCmd.OutOrStdout())\n\tfor stdin.Scan() {\n\t\tline, err := envsubst.EvalEnv(stdin.Text(), envsubstArgs.strict)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = fmt.Fprintln(stdout, line)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = stdout.Flush()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/envsubst_test.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"testing\"\n\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestEnvsubst(t *testing.T) {\n\tg := NewWithT(t)\n\tinput, err := os.ReadFile(\"testdata/envsubst/file.yaml\")\n\tg.Expect(err).NotTo(HaveOccurred())\n\n\tt.Setenv(\"REPO_NAME\", \"test\")\n\n\toutput, err := executeCommandWithIn(\"envsubst\", bytes.NewReader(input))\n\tg.Expect(err).NotTo(HaveOccurred())\n\n\texpected, err := os.ReadFile(\"testdata/envsubst/file.gold\")\n\tg.Expect(err).NotTo(HaveOccurred())\n\tg.Expect(output).To(Equal(string(expected)))\n}\n\nfunc TestEnvsubst_Strinct(t *testing.T) {\n\tg := NewWithT(t)\n\tinput, err := os.ReadFile(\"testdata/envsubst/file.yaml\")\n\tg.Expect(err).NotTo(HaveOccurred())\n\n\t_, err = executeCommandWithIn(\"envsubst --strict\", bytes.NewReader(input))\n\tg.Expect(err).To(HaveOccurred())\n\tg.Expect(err.Error()).To(ContainSubstring(\"variable not set (strict mode)\"))\n}\n"
  },
  {
    "path": "cmd/flux/events.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/fields\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/duration\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\truntimeresource \"k8s.io/cli-runtime/pkg/resource\"\n\tcmdutil \"k8s.io/kubectl/pkg/cmd/util\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n\tnotificationv1b3 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\tswapi \"github.com/fluxcd/source-watcher/api/v2/v1beta1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/printers\"\n)\n\nvar eventsCmd = &cobra.Command{\n\tUse:   \"events\",\n\tShort: \"Display Kubernetes events for Flux resources\",\n\tLong:  withPreviewNote(\"The events sub-command shows Kubernetes events from Flux resources\"),\n\tExample: ` # Display events for flux resources in default namespace\n\tflux events -n default\n\t\n\t# Display events for flux resources in all namespaces\n\tflux events -A\n\n\t# Display events for a Kustomization named podinfo\n\tflux events --for Kustomization/podinfo\n\n\t# Display events for all Kustomizations in default namespace\n\tflux events --for Kustomization -n default\n\n\t# Display warning events for alert resources\n\tflux events --for Alert/podinfo --types warning\n`,\n\tRunE: eventsCmdRun,\n}\n\ntype eventFlags struct {\n\tallNamespaces bool\n\twatch         bool\n\tforSelector   string\n\tfilterTypes   []string\n}\n\nvar eventArgs eventFlags\n\nfunc init() {\n\teventsCmd.Flags().BoolVarP(&eventArgs.allNamespaces, \"all-namespaces\", \"A\", false,\n\t\t\"display events from Flux resources across all namespaces\")\n\teventsCmd.Flags().BoolVarP(&eventArgs.watch, \"watch\", \"w\", false,\n\t\t\"indicate if the events should be streamed\")\n\teventsCmd.Flags().StringVar(&eventArgs.forSelector, \"for\", \"\",\n\t\t\"get events for a particular object\")\n\teventsCmd.Flags().StringSliceVar(&eventArgs.filterTypes, \"types\", []string{}, \"filter events for certain types (valid types are: Normal, Warning)\")\n\trootCmd.AddCommand(eventsCmd)\n}\n\nfunc eventsCmdRun(cmd *cobra.Command, args []string) error {\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tif err := validateEventTypes(eventArgs.filterTypes); err != nil {\n\t\treturn err\n\t}\n\n\tkubeclient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnamespace := *kubeconfigArgs.Namespace\n\tif eventArgs.allNamespaces {\n\t\tnamespace = \"\"\n\t}\n\n\tvar diffRefNs bool\n\t// Build the base list options. When --all-namespaces is set we must NOT constrain the\n\t// query to a single namespace, otherwise we silently return a partial result set.\n\tclientListOpts := []client.ListOption{}\n\tif !eventArgs.allNamespaces {\n\t\tclientListOpts = append(clientListOpts, client.InNamespace(*kubeconfigArgs.Namespace))\n\t}\n\tvar refListOpts [][]client.ListOption\n\tif eventArgs.forSelector != \"\" {\n\t\tkind, name := getKindNameFromSelector(eventArgs.forSelector)\n\t\tif kind == \"\" {\n\t\t\treturn fmt.Errorf(\"--for selector must be of format <kind>[/<name>]\")\n\t\t}\n\n\t\trefInfoKind, err := fluxKindMap.getRefInfo(kind)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tclientListOpts = append(clientListOpts, getListOpt(refInfoKind.gvk.Kind, name))\n\t\tif name != \"\" {\n\t\t\trefs, err := getObjectRef(ctx, kubeclient, refInfoKind, name, *kubeconfigArgs.Namespace)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfor _, ref := range refs {\n\t\t\t\trefKind, refName, refNs := utils.ParseObjectKindNameNamespace(ref)\n\t\t\t\tif refNs != namespace {\n\t\t\t\t\tdiffRefNs = true\n\t\t\t\t}\n\t\t\t\trefOpt := []client.ListOption{getListOpt(refKind, refName), client.InNamespace(refNs)}\n\t\t\t\trefListOpts = append(refListOpts, refOpt)\n\t\t\t}\n\t\t}\n\t}\n\n\tshowNamespace := namespace == \"\" || diffRefNs\n\tif eventArgs.watch {\n\t\treturn eventsCmdWatchRun(ctx, kubeclient, clientListOpts, refListOpts, showNamespace)\n\t}\n\n\trows, err := getRows(ctx, kubeclient, clientListOpts, refListOpts, showNamespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(rows) == 0 {\n\t\tif eventArgs.allNamespaces {\n\t\t\tlogger.Failuref(\"No events found.\")\n\t\t} else {\n\t\t\tlogger.Failuref(\"No events found in %s namespace.\", *kubeconfigArgs.Namespace)\n\t\t}\n\n\t\treturn nil\n\t}\n\theaders := getHeaders(showNamespace)\n\treturn printers.TablePrinter(headers).Print(cmd.OutOrStdout(), rows)\n}\n\nfunc getRows(ctx context.Context, kubeclient client.Client, clientListOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) ([][]string, error) {\n\tel := &corev1.EventList{}\n\tif err := addEventsToList(ctx, kubeclient, el, clientListOpts); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, refOpts := range refListOpts {\n\t\tif err := addEventsToList(ctx, kubeclient, el, refOpts); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsort.Sort(SortableEvents(el.Items))\n\n\tvar rows [][]string\n\tfor _, item := range el.Items {\n\t\tif ignoreEvent(item) {\n\t\t\tcontinue\n\t\t}\n\t\trows = append(rows, getEventRow(item, showNs))\n\t}\n\n\treturn rows, nil\n}\n\nfunc addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.EventList, clientListOpts []client.ListOption) error {\n\tlistOpts := &metav1.ListOptions{}\n\terr := runtimeresource.FollowContinue(listOpts,\n\t\tfunc(options metav1.ListOptions) (runtime.Object, error) {\n\t\t\tnewEvents := &corev1.EventList{}\n\t\t\topts := append(clientListOpts, client.Limit(cmdutil.DefaultChunkSize))\n\t\t\tif options.Continue != \"\" {\n\t\t\t\topts = append(opts, client.Continue(options.Continue))\n\t\t\t}\n\t\t\tif err := kubeclient.List(ctx, newEvents, opts...); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error getting events: %w\", err)\n\t\t\t}\n\t\t\tel.Items = append(el.Items, newEvents.Items...)\n\t\t\treturn newEvents, nil\n\t\t})\n\n\treturn err\n}\n\nfunc getListOpt(kind, name string) client.ListOption {\n\tvar sel fields.Selector\n\tif name == \"\" {\n\t\tsel = fields.OneTermEqualSelector(\"involvedObject.kind\", kind)\n\t} else {\n\t\tsel = fields.AndSelectors(\n\t\t\tfields.OneTermEqualSelector(\"involvedObject.kind\", kind),\n\t\t\tfields.OneTermEqualSelector(\"involvedObject.name\", name))\n\t}\n\n\treturn client.MatchingFieldsSelector{Selector: sel}\n}\n\nfunc eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) error {\n\tevent := &corev1.EventList{}\n\tlistOpts = append(listOpts, client.Limit(cmdutil.DefaultChunkSize))\n\teventWatch, err := kubeclient.Watch(ctx, event, listOpts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfirstIteration := true\n\n\thandleEvent := func(e watch.Event) error {\n\t\tif e.Type == watch.Deleted {\n\t\t\treturn nil\n\t\t}\n\n\t\tevent, ok := e.Object.(*corev1.Event)\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t\tif ignoreEvent(*event) {\n\t\t\treturn nil\n\t\t}\n\t\trows := getEventRow(*event, showNs)\n\t\tvar hdr []string\n\t\tif firstIteration {\n\t\t\thdr = getHeaders(showNs)\n\t\t\tfirstIteration = false\n\t\t}\n\t\treturn printers.TablePrinter(hdr).Print(rootCmd.OutOrStdout(), [][]string{rows})\n\t}\n\n\tfor _, refOpts := range refListOpts {\n\t\trefEventWatch, err := kubeclient.Watch(ctx, event, refOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgo func() {\n\t\t\tif err := receiveEventChan(ctx, refEventWatch, handleEvent); err != nil {\n\t\t\t\tlogger.Failuref(\"error watching events: %s\", err.Error())\n\t\t\t}\n\t\t}()\n\t}\n\n\treturn receiveEventChan(ctx, eventWatch, handleEvent)\n}\n\nfunc receiveEventChan(ctx context.Context, eventWatch watch.Interface, f func(e watch.Event) error) error {\n\tdefer eventWatch.Stop()\n\tfor {\n\t\tselect {\n\t\tcase e, ok := <-eventWatch.ResultChan():\n\t\t\tif !ok {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\terr := f(e)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc getHeaders(showNs bool) []string {\n\theaders := []string{\"Last seen\", \"Type\", \"Reason\", \"Object\", \"Message\"}\n\tif showNs {\n\t\theaders = append(namespaceHeader, headers...)\n\t}\n\n\treturn headers\n}\n\nfunc getEventRow(e corev1.Event, showNs bool) []string {\n\tvar row []string\n\tif showNs {\n\t\trow = []string{e.Namespace}\n\t}\n\trow = append(row, getLastSeen(e), e.Type, e.Reason, fmt.Sprintf(\"%s/%s\", e.InvolvedObject.Kind, e.InvolvedObject.Name), e.Message)\n\n\treturn row\n}\n\n// getObjectRef is used to get the metadata of a resource that the selector(in the format <kind/name>) references.\n// It returns an empty string if the resource doesn't reference any resource\n// and a string with the format `<kind>/<name>.<namespace>` if it does.\nfunc getObjectRef(ctx context.Context, kubeclient client.Client, ref refInfo, name, ns string) ([]string, error) {\n\t// the resource has no source ref\n\tif len(ref.field) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tobj := &unstructured.Unstructured{}\n\tobj.SetGroupVersionKind(schema.GroupVersionKind{\n\t\tKind:    ref.gvk.Kind,\n\t\tVersion: ref.gvk.Version,\n\t\tGroup:   ref.gvk.Group,\n\t})\n\tobjName := types.NamespacedName{\n\t\tNamespace: ns,\n\t\tName:      name,\n\t}\n\n\tif err := kubeclient.Get(ctx, objName, obj); err != nil {\n\t\treturn nil, err\n\t}\n\n\trefKind := ref.kind\n\tif refKind == \"\" {\n\t\tkindField := append(ref.field, \"kind\")\n\t\tspecKind, ok, err := unstructured.NestedString(obj.Object, kindField...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"field '%s' for '%s' not found\", strings.Join(kindField, \".\"), objName)\n\t\t}\n\t\trefKind = specKind\n\t}\n\n\tnameField := append(ref.field, \"name\")\n\trefName, ok, err := unstructured.NestedString(obj.Object, nameField...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"field '%s' for '%s' not found\", strings.Join(nameField, \".\"), objName)\n\t}\n\n\tvar allRefs []string\n\trefNamespace := ns\n\tif ref.crossNamespaced {\n\t\tnamespaceField := append(ref.field, \"namespace\")\n\t\tnamespace, ok, err := unstructured.NestedString(obj.Object, namespaceField...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif ok {\n\t\t\trefNamespace = namespace\n\t\t}\n\t}\n\n\tallRefs = append(allRefs, fmt.Sprintf(\"%s/%s.%s\", refKind, refName, refNamespace))\n\tif ref.otherRefs != nil {\n\t\tfor _, otherRef := range ref.otherRefs(ns, name) {\n\t\t\tallRefs = append(allRefs, fmt.Sprintf(\"%s.%s\", otherRef, refNamespace))\n\t\t}\n\t}\n\treturn allRefs, nil\n}\n\ntype refMap map[string]refInfo\n\nfunc (r refMap) getRefInfo(kind string) (refInfo, error) {\n\tfor key, ref := range r {\n\t\tif strings.EqualFold(key, kind) {\n\t\t\treturn ref, nil\n\t\t}\n\t}\n\treturn refInfo{}, fmt.Errorf(\"'%s' is not a recognized Flux kind\", kind)\n}\n\nfunc (r refMap) hasKind(kind string) bool {\n\t_, err := r.getRefInfo(kind)\n\treturn err == nil\n}\n\n// validateEventTypes checks that the event types passed into the function\n// is either equal to `Normal` or `Warning` which are currently the two supported types.\n// https://github.com/kubernetes/kubernetes/blob/a8a1abc25cad87333840cd7d54be2efaf31a3177/staging/src/k8s.io/api/core/v1/types.go#L6212\nfunc validateEventTypes(eventTypes []string) error {\n\tfor _, t := range eventTypes {\n\t\tif !strings.EqualFold(corev1.EventTypeWarning, t) && !strings.EqualFold(corev1.EventTypeNormal, t) {\n\t\t\treturn fmt.Errorf(\"type '%s' not supported. Supported types are Normal, Warning\", t)\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype refInfo struct {\n\t// gvk is the group version kind of the resource\n\tgvk schema.GroupVersionKind\n\t// kind is the kind that the resource references if it's not static\n\tkind string\n\t// crossNamespaced indicates if this resource uses cross namespaced references\n\tcrossNamespaced bool\n\t// otherRefs returns other reference that might not be directly accessible\n\t// from the spec of the object\n\totherRefs func(namespace, name string) []string\n\tfield     []string\n}\n\nvar fluxKindMap = refMap{\n\tkustomizev1.KustomizationKind: {\n\t\tgvk:             kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind),\n\t\tcrossNamespaced: true,\n\t\tfield:           []string{\"spec\", \"sourceRef\"},\n\t},\n\thelmv2.HelmReleaseKind: {\n\t\tgvk:             helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind),\n\t\tcrossNamespaced: true,\n\t\totherRefs: func(namespace, name string) []string {\n\t\t\treturn []string{fmt.Sprintf(\"%s/%s-%s\", sourcev1.HelmChartKind, namespace, name)}\n\t\t},\n\t\tfield: []string{\"spec\", \"chart\", \"spec\", \"sourceRef\"},\n\t},\n\tnotificationv1b3.AlertKind: {\n\t\tgvk:             notificationv1b3.GroupVersion.WithKind(notificationv1b3.AlertKind),\n\t\tkind:            notificationv1b3.ProviderKind,\n\t\tcrossNamespaced: false,\n\t\tfield:           []string{\"spec\", \"providerRef\"},\n\t},\n\tnotificationv1.ReceiverKind:   {gvk: notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)},\n\tnotificationv1b3.ProviderKind: {gvk: notificationv1b3.GroupVersion.WithKind(notificationv1b3.ProviderKind)},\n\timagev1.ImagePolicyKind: {\n\t\tgvk:             imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind),\n\t\tkind:            imagev1.ImageRepositoryKind,\n\t\tcrossNamespaced: true,\n\t\tfield:           []string{\"spec\", \"imageRepositoryRef\"},\n\t},\n\tsourcev1.HelmChartKind: {\n\t\tgvk:             sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind),\n\t\tcrossNamespaced: true,\n\t\tfield:           []string{\"spec\", \"sourceRef\"},\n\t},\n\tsourcev1.GitRepositoryKind:       {gvk: sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)},\n\tsourcev1.OCIRepositoryKind:       {gvk: sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)},\n\tsourcev1.BucketKind:              {gvk: sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)},\n\tsourcev1.HelmRepositoryKind:      {gvk: sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)},\n\tautov1.ImageUpdateAutomationKind: {gvk: autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)},\n\timagev1.ImageRepositoryKind:      {gvk: imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)},\n\tswapi.ArtifactGeneratorKind:      {gvk: swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)},\n}\n\nfunc ignoreEvent(e corev1.Event) bool {\n\tif !fluxKindMap.hasKind(e.InvolvedObject.Kind) {\n\t\treturn true\n\t}\n\n\tif len(eventArgs.filterTypes) > 0 {\n\t\t_, equal := utils.ContainsEqualFoldItemString(eventArgs.filterTypes, e.Type)\n\t\tif !equal {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc getKindNameFromSelector(selector string) (string, string) {\n\tkind, name := utils.ParseObjectKindName(selector)\n\t// if there's no slash in the selector utils.ParseObjectKindName returns the\n\t// input string as the name but here we want it as the kind instead\n\tif kind == \"\" && name != \"\" {\n\t\tkind = name\n\t\tname = \"\"\n\t}\n\n\treturn kind, name\n}\n\n// The functions below are copied from: https://github.com/kubernetes/kubectl/blob/4ecd7bd0f0799f191335a331ca3c6a397a888233/pkg/cmd/events/events.go#L294\n\n// SortableEvents implements sort.Interface for []api.Event by time\ntype SortableEvents []corev1.Event\n\nfunc (list SortableEvents) Len() int {\n\treturn len(list)\n}\n\nfunc (list SortableEvents) Swap(i, j int) {\n\tlist[i], list[j] = list[j], list[i]\n}\n\n// Return the time that should be used for sorting, which can come from\n// various places in corev1.Event.\nfunc eventTime(event corev1.Event) time.Time {\n\tif event.Series != nil {\n\t\treturn event.Series.LastObservedTime.Time\n\t}\n\tif !event.LastTimestamp.Time.IsZero() {\n\t\treturn event.LastTimestamp.Time\n\t}\n\treturn event.EventTime.Time\n}\n\nfunc (list SortableEvents) Less(i, j int) bool {\n\treturn eventTime(list[i]).Before(eventTime(list[j]))\n}\n\nfunc getLastSeen(e corev1.Event) string {\n\tvar interval string\n\tfirstTimestampSince := translateMicroTimestampSince(e.EventTime)\n\tif e.EventTime.IsZero() {\n\t\tfirstTimestampSince = translateTimestampSince(e.FirstTimestamp)\n\t}\n\tif e.Series != nil {\n\t\tinterval = fmt.Sprintf(\"%s (x%d over %s)\", translateMicroTimestampSince(e.Series.LastObservedTime), e.Series.Count, firstTimestampSince)\n\t} else if e.Count > 1 {\n\t\tinterval = fmt.Sprintf(\"%s (x%d over %s)\", translateTimestampSince(e.LastTimestamp), e.Count, firstTimestampSince)\n\t} else {\n\t\tinterval = firstTimestampSince\n\t}\n\n\treturn interval\n}\n\n// translateMicroTimestampSince returns the elapsed time since timestamp in\n// human-readable approximation.\nfunc translateMicroTimestampSince(timestamp metav1.MicroTime) string {\n\tif timestamp.IsZero() {\n\t\treturn \"<unknown>\"\n\t}\n\n\treturn duration.HumanDuration(time.Since(timestamp.Time))\n}\n\n// translateTimestampSince returns the elapsed time since timestamp in\n// human-readable approximation.\nfunc translateTimestampSince(timestamp metav1.Time) string {\n\tif timestamp.IsZero() {\n\t\treturn \"<unknown>\"\n\t}\n\n\treturn duration.HumanDuration(time.Since(timestamp.Time))\n}\n"
  },
  {
    "path": "cmd/flux/events_test.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t. \"github.com/onsi/gomega\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/fields\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client/fake\"\n\n\teventv1 \"github.com/fluxcd/pkg/apis/event/v1beta1\"\n\tssautil \"github.com/fluxcd/pkg/ssa/utils\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar objects = `\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: flux-system\n  namespace: flux-system\nspec:\n  interval: 5m0s\n  path: ./infrastructure/\n  prune: true\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: podinfo\n  namespace: default\nspec:\n  interval: 5m0s\n  path: ./infrastructure/\n  prune: true\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n    namespace: flux-system\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: flux-system\n  namespace: flux-system\nspec:\n  interval: 5m0s\n  ref:\n    branch: main\n  secretRef:\n    name: flux-system\n  timeout: 1m0s\n  url: ssh://git@github.com/example/repo\n---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: podinfo\n  namespace: default\nspec:\n  chart:\n    spec:\n      chart: podinfo\n      reconcileStrategy: ChartVersion\n      sourceRef:\n        kind: HelmRepository\n        name: podinfo\n        namespace: flux-system\n      version: '*'\n  interval: 5m0s\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 1m0s\n  url: https://stefanprodan.github.io/podinfo\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmChart\nmetadata:\n  name: default-podinfo\n  namespace: flux-system\nspec:\n  chart: podinfo\n  interval: 1m0s\n  reconcileStrategy: ChartVersion\n  sourceRef:\n    kind: HelmRepository\n    name: podinfo-chart\n  version: '*'\n---\napiVersion: notification.toolkit.fluxcd.io/v1beta3\nkind: Alert\nmetadata:\n  name: webapp\n  namespace: flux-system\nspec:\n  eventSeverity: info\n  eventSources:\n  - kind: GitRepository\n    name: '*'\n  providerRef:\n    name: slack\n---\napiVersion: notification.toolkit.fluxcd.io/v1beta3\nkind: Provider\nmetadata:\n  name: slack\n  namespace: flux-system\nspec:\n  address: https://hooks.slack.com/services/mock\n  type: slack\n---\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImagePolicy\nmetadata:\n  name: podinfo\n  namespace: default\nspec:\n  imageRepositoryRef:\n    name: acr-podinfo\n    namespace: flux-system\n  policy:\n    semver:\n      range: 5.0.x\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: flux-system`\n\nfunc Test_getObjectRef(t *testing.T) {\n\tg := NewWithT(t)\n\tobjs, err := ssautil.ReadObjects(strings.NewReader(objects))\n\tg.Expect(err).To(Not(HaveOccurred()))\n\n\tbuilder := fake.NewClientBuilder().WithScheme(utils.NewScheme())\n\tfor _, obj := range objs {\n\t\tbuilder = builder.WithObjects(obj)\n\t}\n\tc := builder.Build()\n\n\ttests := []struct {\n\t\tname      string\n\t\tselector  string\n\t\tnamespace string\n\t\twant      []string\n\t\twantErr   bool\n\t}{\n\t\t{\n\t\t\tname:      \"Source Ref for Kustomization\",\n\t\t\tselector:  \"Kustomization/flux-system\",\n\t\t\tnamespace: \"flux-system\",\n\t\t\twant:      []string{\"GitRepository/flux-system.flux-system\"},\n\t\t},\n\t\t{\n\t\t\tname:      \"Crossnamespace Source Ref for Kustomization\",\n\t\t\tselector:  \"Kustomization/podinfo\",\n\t\t\tnamespace: \"default\",\n\t\t\twant:      []string{\"GitRepository/flux-system.flux-system\"},\n\t\t},\n\t\t{\n\t\t\tname:      \"Source Ref for HelmRelease\",\n\t\t\tselector:  \"HelmRelease/podinfo\",\n\t\t\tnamespace: \"default\",\n\t\t\twant:      []string{\"HelmRepository/podinfo.flux-system\", \"HelmChart/default-podinfo.flux-system\"},\n\t\t},\n\t\t{\n\t\t\tname:      \"Source Ref for Alert\",\n\t\t\tselector:  \"Alert/webapp\",\n\t\t\tnamespace: \"flux-system\",\n\t\t\twant:      []string{\"Provider/slack.flux-system\"},\n\t\t},\n\t\t{\n\t\t\tname:      \"Source Ref for ImagePolicy\",\n\t\t\tselector:  \"ImagePolicy/podinfo\",\n\t\t\tnamespace: \"default\",\n\t\t\twant:      []string{\"ImageRepository/acr-podinfo.flux-system\"},\n\t\t},\n\t\t{\n\t\t\tname:      \"Source Ref for ImagePolicy (lowercased)\",\n\t\t\tselector:  \"imagepolicy/podinfo\",\n\t\t\tnamespace: \"default\",\n\t\t\twant:      []string{\"ImageRepository/acr-podinfo.flux-system\"},\n\t\t},\n\t\t{\n\t\t\tname:      \"Empty Ref for Provider\",\n\t\t\tselector:  \"Provider/slack\",\n\t\t\tnamespace: \"flux-system\",\n\t\t\twant:      nil,\n\t\t},\n\t\t{\n\t\t\tname:     \"Non flux resource\",\n\t\t\tselector: \"Namespace/flux-system\",\n\t\t\twantErr:  true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tg := NewWithT(t)\n\t\t\tkind, name := getKindNameFromSelector(tt.selector)\n\t\t\tinfoRef, err := fluxKindMap.getRefInfo(kind)\n\t\t\tif tt.wantErr {\n\t\t\t\tg.Expect(err).To(HaveOccurred())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgot, err := getObjectRef(context.Background(), c, infoRef, name, tt.namespace)\n\n\t\t\tg.Expect(err).To(Not(HaveOccurred()))\n\t\t\tg.Expect(got).To(Equal(tt.want))\n\t\t})\n\t}\n}\n\nfunc Test_getRows(t *testing.T) {\n\tg := NewWithT(t)\n\tobjs, err := ssautil.ReadObjects(strings.NewReader(objects))\n\tg.Expect(err).To(Not(HaveOccurred()))\n\n\tbuilder := fake.NewClientBuilder().WithScheme(utils.NewScheme())\n\tfor _, obj := range objs {\n\t\tbuilder = builder.WithObjects(obj)\n\t}\n\teventList := &corev1.EventList{}\n\tfor _, obj := range objs {\n\t\tinfoEvent := createEvent(obj, eventv1.EventSeverityInfo, \"Info Message\", \"Info Reason\")\n\t\twarningEvent := createEvent(obj, eventv1.EventSeverityError, \"Error Message\", \"Error Reason\")\n\t\teventList.Items = append(eventList.Items, infoEvent, warningEvent)\n\t}\n\tbuilder = builder.WithLists(eventList)\n\tbuilder.WithIndex(&corev1.Event{}, \"involvedObject.kind/name\", kindNameIndexer)\n\tbuilder.WithIndex(&corev1.Event{}, \"involvedObject.kind\", kindIndexer)\n\tc := builder.Build()\n\n\ttests := []struct {\n\t\tname        string\n\t\tselector    string\n\t\trefSelector string\n\t\tnamespace   string\n\t\trefNs       string\n\t\texpected    [][]string\n\t}{\n\t\t{\n\t\t\tname:      \"events from all namespaces\",\n\t\t\tselector:  \"\",\n\t\t\tnamespace: \"\",\n\t\t\texpected: [][]string{\n\t\t\t\t{\"default\", \"<unknown>\", \"error\", \"Error Reason\", \"HelmRelease/podinfo\", \"Error Message\"},\n\t\t\t\t{\"default\", \"<unknown>\", \"info\", \"Info Reason\", \"HelmRelease/podinfo\", \"Info Message\"},\n\t\t\t\t{\"default\", \"<unknown>\", \"error\", \"Error Reason\", \"ImagePolicy/podinfo\", \"Error Message\"},\n\t\t\t\t{\"default\", \"<unknown>\", \"info\", \"Info Reason\", \"ImagePolicy/podinfo\", \"Info Message\"},\n\t\t\t\t{\"default\", \"<unknown>\", \"error\", \"Error Reason\", \"Kustomization/podinfo\", \"Error Message\"},\n\t\t\t\t{\"default\", \"<unknown>\", \"info\", \"Info Reason\", \"Kustomization/podinfo\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"Alert/webapp\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"Alert/webapp\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"GitRepository/flux-system\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"GitRepository/flux-system\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"HelmChart/default-podinfo\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"HelmChart/default-podinfo\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"HelmRepository/podinfo\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"HelmRepository/podinfo\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"Kustomization/flux-system\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"Kustomization/flux-system\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"Provider/slack\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"Provider/slack\", \"Info Message\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"events from default namespaces\",\n\t\t\tselector:  \"\",\n\t\t\tnamespace: \"default\",\n\t\t\texpected: [][]string{\n\t\t\t\t{\"<unknown>\", \"error\", \"Error Reason\", \"HelmRelease/podinfo\", \"Error Message\"},\n\t\t\t\t{\"<unknown>\", \"info\", \"Info Reason\", \"HelmRelease/podinfo\", \"Info Message\"},\n\t\t\t\t{\"<unknown>\", \"error\", \"Error Reason\", \"ImagePolicy/podinfo\", \"Error Message\"},\n\t\t\t\t{\"<unknown>\", \"info\", \"Info Reason\", \"ImagePolicy/podinfo\", \"Info Message\"},\n\t\t\t\t{\"<unknown>\", \"error\", \"Error Reason\", \"Kustomization/podinfo\", \"Error Message\"},\n\t\t\t\t{\"<unknown>\", \"info\", \"Info Reason\", \"Kustomization/podinfo\", \"Info Message\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"Kustomization with crossnamespaced GitRepository\",\n\t\t\tselector:  \"Kustomization/podinfo\",\n\t\t\tnamespace: \"default\",\n\t\t\texpected: [][]string{\n\t\t\t\t{\"default\", \"<unknown>\", \"error\", \"Error Reason\", \"Kustomization/podinfo\", \"Error Message\"},\n\t\t\t\t{\"default\", \"<unknown>\", \"info\", \"Info Reason\", \"Kustomization/podinfo\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"GitRepository/flux-system\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"GitRepository/flux-system\", \"Info Message\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"All Kustomization (lowercased selector)\",\n\t\t\tselector: \"kustomization\",\n\t\t\texpected: [][]string{\n\t\t\t\t{\"default\", \"<unknown>\", \"error\", \"Error Reason\", \"Kustomization/podinfo\", \"Error Message\"},\n\t\t\t\t{\"default\", \"<unknown>\", \"info\", \"Info Reason\", \"Kustomization/podinfo\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"Kustomization/flux-system\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"Kustomization/flux-system\", \"Info Message\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"HelmRelease with crossnamespaced HelmRepository\",\n\t\t\tselector:  \"HelmRelease/podinfo\",\n\t\t\tnamespace: \"default\",\n\t\t\texpected: [][]string{\n\t\t\t\t{\"default\", \"<unknown>\", \"error\", \"Error Reason\", \"HelmRelease/podinfo\", \"Error Message\"},\n\t\t\t\t{\"default\", \"<unknown>\", \"info\", \"Info Reason\", \"HelmRelease/podinfo\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"HelmRepository/podinfo\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"HelmRepository/podinfo\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"HelmChart/default-podinfo\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"HelmChart/default-podinfo\", \"Info Message\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"HelmRelease with crossnamespaced HelmRepository (lowercased)\",\n\t\t\tselector:  \"helmrelease/podinfo\",\n\t\t\tnamespace: \"default\",\n\t\t\texpected: [][]string{\n\t\t\t\t{\"default\", \"<unknown>\", \"error\", \"Error Reason\", \"HelmRelease/podinfo\", \"Error Message\"},\n\t\t\t\t{\"default\", \"<unknown>\", \"info\", \"Info Reason\", \"HelmRelease/podinfo\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"HelmRepository/podinfo\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"HelmRepository/podinfo\", \"Info Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"error\", \"Error Reason\", \"HelmChart/default-podinfo\", \"Error Message\"},\n\t\t\t\t{\"flux-system\", \"<unknown>\", \"info\", \"Info Reason\", \"HelmChart/default-podinfo\", \"Info Message\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tg := NewWithT(t)\n\n\t\t\tvar refs []string\n\t\t\tvar refNs, refKind, refName string\n\t\t\tvar clientOpts = []client.ListOption{client.InNamespace(tt.namespace)}\n\t\t\tif tt.selector != \"\" {\n\t\t\t\tkind, name := getKindNameFromSelector(tt.selector)\n\t\t\t\tinfoRef, err := fluxKindMap.getRefInfo(kind)\n\t\t\t\tclientOpts = append(clientOpts, getTestListOpt(infoRef.gvk.Kind, name))\n\t\t\t\tif name != \"\" {\n\t\t\t\t\tg.Expect(err).To(Not(HaveOccurred()))\n\t\t\t\t\trefs, err = getObjectRef(context.Background(), c, infoRef, name, tt.namespace)\n\t\t\t\t\tg.Expect(err).To(Not(HaveOccurred()))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tg.Expect(err).To(Not(HaveOccurred()))\n\n\t\t\tvar refOpts [][]client.ListOption\n\t\t\tfor _, ref := range refs {\n\t\t\t\trefKind, refName, refNs = utils.ParseObjectKindNameNamespace(ref)\n\t\t\t\trefOpts = append(refOpts, []client.ListOption{client.InNamespace(refNs), getTestListOpt(refKind, refName)})\n\t\t\t}\n\n\t\t\tshowNs := tt.namespace == \"\" || (refNs != \"\" && refNs != tt.namespace)\n\t\t\trows, err := getRows(context.Background(), c, clientOpts, refOpts, showNs)\n\t\t\tg.Expect(err).To(Not(HaveOccurred()))\n\t\t\tg.Expect(rows).To(ConsistOf(tt.expected))\n\t\t})\n\t}\n}\n\nfunc getTestListOpt(kind, name string) client.ListOption {\n\tvar sel fields.Selector\n\tif name == \"\" {\n\t\tsel = fields.OneTermEqualSelector(\"involvedObject.kind\", kind)\n\t} else {\n\t\tsel = fields.OneTermEqualSelector(\"involvedObject.kind/name\", fmt.Sprintf(\"%s/%s\", kind, name))\n\t}\n\treturn client.MatchingFieldsSelector{Selector: sel}\n}\n\nfunc createEvent(obj client.Object, eventType, msg, reason string) corev1.Event {\n\treturn corev1.Event{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace: obj.GetNamespace(),\n\t\t\t// name of event needs to be unique\n\t\t\tName: obj.GetNamespace() + obj.GetNamespace() + obj.GetObjectKind().GroupVersionKind().Kind + eventType,\n\t\t},\n\t\tReason:  reason,\n\t\tMessage: msg,\n\t\tType:    eventType,\n\t\tInvolvedObject: corev1.ObjectReference{\n\t\t\tKind:      obj.GetObjectKind().GroupVersionKind().Kind,\n\t\t\tNamespace: obj.GetNamespace(),\n\t\t\tName:      obj.GetName(),\n\t\t},\n\t}\n}\n\n// paginatedClient wraps a client.Client and simulates real Kubernetes API\n// pagination by splitting List results into pages of pageSize items,\n// using the ListMeta.Continue token.\ntype paginatedClient struct {\n\tclient.Client\n\tpageSize int\n}\n\nfunc (c *paginatedClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {\n\tlistOpts := &client.ListOptions{}\n\tlistOpts.ApplyOptions(opts)\n\n\t// Fetch all results from the underlying client (without Limit/Continue).\n\tstripped := make([]client.ListOption, 0, len(opts))\n\tfor _, o := range opts {\n\t\tif _, ok := o.(client.Limit); ok {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := o.(client.Continue); ok {\n\t\t\tcontinue\n\t\t}\n\t\tstripped = append(stripped, o)\n\t}\n\tif err := c.Client.List(ctx, list, stripped...); err != nil {\n\t\treturn err\n\t}\n\n\titems, err := meta.ExtractList(list)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Determine the page window based on the Continue token.\n\tstart := 0\n\tif listOpts.Continue != \"\" {\n\t\tn, err := strconv.Atoi(listOpts.Continue)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid continue token: %w\", err)\n\t\t}\n\t\tstart = n\n\t}\n\tif start > len(items) {\n\t\tstart = len(items)\n\t}\n\n\tend := start + c.pageSize\n\tif end > len(items) {\n\t\tend = len(items)\n\t}\n\n\tpage := items[start:end]\n\tif err := meta.SetList(list, page); err != nil {\n\t\treturn err\n\t}\n\n\t// Set the Continue token when there are more pages.\n\tlistAccessor, err := meta.ListAccessor(list)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif end < len(items) {\n\t\tlistAccessor.SetContinue(strconv.Itoa(end))\n\t} else {\n\t\tlistAccessor.SetContinue(\"\")\n\t}\n\n\treturn nil\n}\n\nfunc Test_addEventsToList_pagination(t *testing.T) {\n\tg := NewWithT(t)\n\tobjs, err := ssautil.ReadObjects(strings.NewReader(objects))\n\tg.Expect(err).To(Not(HaveOccurred()))\n\n\tbuilder := fake.NewClientBuilder().WithScheme(utils.NewScheme())\n\tfor _, obj := range objs {\n\t\tbuilder = builder.WithObjects(obj)\n\t}\n\n\teventList := &corev1.EventList{}\n\tfor _, obj := range objs {\n\t\tinfoEvent := createEvent(obj, eventv1.EventSeverityInfo, \"Info Message\", \"Info Reason\")\n\t\twarningEvent := createEvent(obj, eventv1.EventSeverityError, \"Error Message\", \"Error Reason\")\n\t\teventList.Items = append(eventList.Items, infoEvent, warningEvent)\n\t}\n\tbuilder = builder.WithLists(eventList)\n\tc := builder.Build()\n\n\ttotalEvents := len(eventList.Items)\n\tg.Expect(totalEvents).To(BeNumerically(\">\", 2), \"need more than 2 events to test pagination\")\n\n\t// Wrap the client to paginate at 2 items per page, forcing multiple\n\t// round-trips through FollowContinue.\n\tpc := &paginatedClient{Client: c, pageSize: 2}\n\n\tel := &corev1.EventList{}\n\terr = addEventsToList(context.Background(), pc, el, nil)\n\tg.Expect(err).To(Not(HaveOccurred()))\n\tg.Expect(el.Items).To(HaveLen(totalEvents),\n\t\t\"addEventsToList should collect all events across paginated responses\")\n}\n\nfunc kindNameIndexer(obj client.Object) []string {\n\te, ok := obj.(*corev1.Event)\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"Expected a Event, got %T\", e))\n\t}\n\n\treturn []string{fmt.Sprintf(\"%s/%s\", e.InvolvedObject.Kind, e.InvolvedObject.Name)}\n}\n\nfunc kindIndexer(obj client.Object) []string {\n\te, ok := obj.(*corev1.Event)\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"Expected a Event, got %T\", e))\n\t}\n\n\treturn []string{e.InvolvedObject.Kind}\n}\n"
  },
  {
    "path": "cmd/flux/export.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar exportCmd = &cobra.Command{\n\tUse:   \"export\",\n\tShort: \"Export resources in YAML format\",\n\tLong:  `The export sub-commands export resources in YAML format.`,\n}\n\ntype exportFlags struct {\n\tall bool\n}\n\nvar exportArgs exportFlags\n\nfunc init() {\n\texportCmd.PersistentFlags().BoolVar(&exportArgs.all, \"all\", false, \"select all resources\")\n\n\trootCmd.AddCommand(exportCmd)\n}\n\n// exportable represents a type that you can fetch from the Kubernetes\n// API, then tidy up for serialising.\ntype exportable interface {\n\tadapter\n\texport() interface{}\n}\n\n// exportableList represents a type that has a list of values, each of\n// which is exportable.\ntype exportableList interface {\n\tlistAdapter\n\texportItem(i int) interface{}\n}\n\ntype exportCommand struct {\n\tobject exportable\n\tlist   exportableList\n}\n\nfunc (export exportCommand) run(cmd *cobra.Command, args []string) error {\n\tif !exportArgs.all && len(args) < 1 {\n\t\treturn fmt.Errorf(\"name is required\")\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif exportArgs.all {\n\t\terr = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(*kubeconfigArgs.Namespace))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif export.list.len() == 0 {\n\t\t\treturn fmt.Errorf(\"no objects found in %s namespace\", *kubeconfigArgs.Namespace)\n\t\t}\n\n\t\tfor i := 0; i < export.list.len(); i++ {\n\t\t\tif err = printExport(export.list.exportItem(i)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t} else {\n\t\tname := args[0]\n\t\tnamespacedName := types.NamespacedName{\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tName:      name,\n\t\t}\n\t\terr = kubeClient.Get(ctx, namespacedName, export.object.asClientObject())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn printExport(export.object.export())\n\t}\n\treturn nil\n}\n\nfunc printExport(export any) error {\n\tdata, err := yaml.Marshal(export)\n\tif err != nil {\n\t\treturn err\n\t}\n\tprintlnStdout(\"---\")\n\tprintlnStdout(resourceToString(data))\n\treturn nil\n}\n\nfunc resourceToString(data []byte) string {\n\tdata = bytes.Replace(data, []byte(\"  creationTimestamp: null\\n\"), []byte(\"\"), 1)\n\tdata = bytes.Replace(data, []byte(\"status: {}\\n\"), []byte(\"\"), 1)\n\tdata = bytes.TrimSpace(data)\n\treturn string(data)\n}\n"
  },
  {
    "path": "cmd/flux/export_alert.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\nvar exportAlertCmd = &cobra.Command{\n\tUse:   \"alert [name]\",\n\tShort: \"Export Alert resources in YAML format\",\n\tLong:  withPreviewNote(\"The export alert command exports one or all Alert resources in YAML format.\"),\n\tExample: `  # Export all Alert resources\n  flux export alert --all > alerts.yaml\n\n  # Export a Alert\n  flux export alert main > main.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),\n\tRunE: exportCommand{\n\t\tobject: alertAdapter{&notificationv1.Alert{}},\n\t\tlist:   alertListAdapter{&notificationv1.AlertList{}},\n\t}.run,\n}\n\nfunc init() {\n\texportCmd.AddCommand(exportAlertCmd)\n}\n\nfunc exportAlert(alert *notificationv1.Alert) interface{} {\n\tgvk := notificationv1.GroupVersion.WithKind(\"Alert\")\n\texport := notificationv1.Alert{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        alert.Name,\n\t\t\tNamespace:   alert.Namespace,\n\t\t\tLabels:      alert.Labels,\n\t\t\tAnnotations: alert.Annotations,\n\t\t},\n\t\tSpec: alert.Spec,\n\t}\n\n\treturn export\n}\n\nfunc (ex alertAdapter) export() interface{} {\n\treturn exportAlert(ex.Alert)\n}\n\nfunc (ex alertListAdapter) exportItem(i int) interface{} {\n\treturn exportAlert(&ex.AlertList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_alertprovider.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\nvar exportAlertProviderCmd = &cobra.Command{\n\tUse:   \"alert-provider [name]\",\n\tShort: \"Export Provider resources in YAML format\",\n\tLong:  withPreviewNote(\"The export alert-provider command exports one or all Provider resources in YAML format.\"),\n\tExample: `  # Export all Provider resources\n  flux export alert-provider --all > alert-providers.yaml\n\n  # Export a Provider\n  flux export alert-provider slack > slack.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),\n\tRunE: exportCommand{\n\t\tobject: alertProviderAdapter{&notificationv1.Provider{}},\n\t\tlist:   alertProviderListAdapter{&notificationv1.ProviderList{}},\n\t}.run,\n}\n\nfunc init() {\n\texportCmd.AddCommand(exportAlertProviderCmd)\n}\n\nfunc exportAlertProvider(alertProvider *notificationv1.Provider) interface{} {\n\tgvk := notificationv1.GroupVersion.WithKind(\"Provider\")\n\texport := notificationv1.Provider{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        alertProvider.Name,\n\t\t\tNamespace:   alertProvider.Namespace,\n\t\t\tLabels:      alertProvider.Labels,\n\t\t\tAnnotations: alertProvider.Annotations,\n\t\t},\n\t\tSpec: alertProvider.Spec,\n\t}\n\treturn export\n}\n\nfunc (ex alertProviderAdapter) export() interface{} {\n\treturn exportAlertProvider(ex.Provider)\n}\n\nfunc (ex alertProviderListAdapter) exportItem(i int) interface{} {\n\treturn exportAlertProvider(&ex.ProviderList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_artifact.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar exportArtifactCmd = &cobra.Command{\n\tUse:   \"artifact\",\n\tShort: \"Export artifact objects\",\n\tLong:  `The export artifact sub-commands export artifacts objects in YAML format.`,\n}\n\nfunc init() {\n\texportCmd.AddCommand(exportArtifactCmd)\n}\n"
  },
  {
    "path": "cmd/flux/export_artifact_generator.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tswapi \"github.com/fluxcd/source-watcher/api/v2/v1beta1\"\n)\n\nvar exportArtifactGeneratorCmd = &cobra.Command{\n\tUse:   \"generator [name]\",\n\tShort: \"Export ArtifactGenerator resources in YAML format\",\n\tLong:  \"The export artifact generator command exports one or all ArtifactGenerator resources in YAML format.\",\n\tExample: `  # Export all ArtifactGenerator resources\n  flux export artifact generator --all > artifact-generators.yaml\n\n  # Export a specific generator\n  flux export artifact generator my-generator > my-generator.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)),\n\tRunE: exportCommand{\n\t\tobject: artifactGeneratorAdapter{&swapi.ArtifactGenerator{}},\n\t\tlist:   artifactGeneratorListAdapter{&swapi.ArtifactGeneratorList{}},\n\t}.run,\n}\n\nfunc init() {\n\texportArtifactCmd.AddCommand(exportArtifactGeneratorCmd)\n}\n\n// Export returns an ArtifactGenerator value which has\n// extraneous information stripped out.\nfunc exportArtifactGenerator(item *swapi.ArtifactGenerator) interface{} {\n\tgvk := swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)\n\texport := swapi.ArtifactGenerator{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        item.Name,\n\t\t\tNamespace:   item.Namespace,\n\t\t\tLabels:      item.Labels,\n\t\t\tAnnotations: item.Annotations,\n\t\t},\n\t\tSpec: item.Spec,\n\t}\n\treturn export\n}\n\nfunc (ex artifactGeneratorAdapter) export() interface{} {\n\treturn exportArtifactGenerator(ex.ArtifactGenerator)\n}\n\nfunc (ex artifactGeneratorListAdapter) exportItem(i int) interface{} {\n\treturn exportArtifactGenerator(&ex.ArtifactGeneratorList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_helmrelease.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n)\n\nvar exportHelmReleaseCmd = &cobra.Command{\n\tUse:     \"helmrelease [name]\",\n\tAliases: []string{\"hr\"},\n\tShort:   \"Export HelmRelease resources in YAML format\",\n\tLong:    \"The export helmrelease command exports one or all HelmRelease resources in YAML format.\",\n\tExample: `  # Export all HelmRelease resources\n  flux export helmrelease --all > kustomizations.yaml\n\n  # Export a HelmRelease\n  flux export hr my-app > app-release.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),\n\tRunE: exportCommand{\n\t\tobject: helmReleaseAdapter{&helmv2.HelmRelease{}},\n\t\tlist:   helmReleaseListAdapter{&helmv2.HelmReleaseList{}},\n\t}.run,\n}\n\nfunc init() {\n\texportCmd.AddCommand(exportHelmReleaseCmd)\n}\n\nfunc exportHelmRelease(helmRelease *helmv2.HelmRelease) interface{} {\n\tgvk := helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)\n\texport := helmv2.HelmRelease{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        helmRelease.Name,\n\t\t\tNamespace:   helmRelease.Namespace,\n\t\t\tLabels:      helmRelease.Labels,\n\t\t\tAnnotations: helmRelease.Annotations,\n\t\t},\n\t\tSpec: helmRelease.Spec,\n\t}\n\treturn export\n}\n\nfunc (ex helmReleaseAdapter) export() interface{} {\n\treturn exportHelmRelease(ex.HelmRelease)\n}\n\nfunc (ex helmReleaseListAdapter) exportItem(i int) interface{} {\n\treturn exportHelmRelease(&ex.HelmReleaseList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_image.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar exportImageCmd = &cobra.Command{\n\tUse:   \"image\",\n\tShort: \"Export image automation objects\",\n\tLong:  `The export image sub-commands export image automation objects in YAML format.`,\n}\n\nfunc init() {\n\texportCmd.AddCommand(exportImageCmd)\n}\n"
  },
  {
    "path": "cmd/flux/export_image_policy.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar exportImagePolicyCmd = &cobra.Command{\n\tUse:   \"policy [name]\",\n\tShort: \"Export ImagePolicy resources in YAML format\",\n\tLong:  \"The export image policy command exports one or all ImagePolicy resources in YAML format.\",\n\tExample: `  # Export all ImagePolicy resources\n  flux export image policy --all > image-policies.yaml\n\n  # Export a specific policy\n  flux export image policy alpine1x > alpine1x.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),\n\tRunE: exportCommand{\n\t\tobject: imagePolicyAdapter{&imagev1.ImagePolicy{}},\n\t\tlist:   imagePolicyListAdapter{&imagev1.ImagePolicyList{}},\n\t}.run,\n}\n\nfunc init() {\n\texportImageCmd.AddCommand(exportImagePolicyCmd)\n}\n\n// Export returns a ImagePolicy value which has extraneous information\n// stripped out.\nfunc exportImagePolicy(item *imagev1.ImagePolicy) interface{} {\n\tgvk := imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)\n\texport := imagev1.ImagePolicy{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        item.Name,\n\t\t\tNamespace:   item.Namespace,\n\t\t\tLabels:      item.Labels,\n\t\t\tAnnotations: item.Annotations,\n\t\t},\n\t\tSpec: item.Spec,\n\t}\n\treturn export\n}\n\nfunc (ex imagePolicyAdapter) export() interface{} {\n\treturn exportImagePolicy(ex.ImagePolicy)\n}\n\nfunc (ex imagePolicyListAdapter) exportItem(i int) interface{} {\n\treturn exportImagePolicy(&ex.ImagePolicyList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_image_repository.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar exportImageRepositoryCmd = &cobra.Command{\n\tUse:   \"repository [name]\",\n\tShort: \"Export ImageRepository resources in YAML format\",\n\tLong:  \"The export image repository command exports one or all ImageRepository resources in YAML format.\",\n\tExample: `  # Export all ImageRepository resources\n  flux export image repository --all > image-repositories.yaml\n\n  # Export a specific ImageRepository resource\n  flux export image repository alpine > alpine.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),\n\tRunE: exportCommand{\n\t\tobject: imageRepositoryAdapter{&imagev1.ImageRepository{}},\n\t\tlist:   imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},\n\t}.run,\n}\n\nfunc init() {\n\texportImageCmd.AddCommand(exportImageRepositoryCmd)\n}\n\nfunc exportImageRepository(repo *imagev1.ImageRepository) interface{} {\n\tgvk := imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)\n\texport := imagev1.ImageRepository{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        repo.Name,\n\t\t\tNamespace:   repo.Namespace,\n\t\t\tLabels:      repo.Labels,\n\t\t\tAnnotations: repo.Annotations,\n\t\t},\n\t\tSpec: repo.Spec,\n\t}\n\treturn export\n}\n\nfunc (ex imageRepositoryAdapter) export() interface{} {\n\treturn exportImageRepository(ex.ImageRepository)\n}\n\nfunc (ex imageRepositoryListAdapter) exportItem(i int) interface{} {\n\treturn exportImageRepository(&ex.ImageRepositoryList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_image_update.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n)\n\nvar exportImageUpdateCmd = &cobra.Command{\n\tUse:   \"update [name]\",\n\tShort: \"Export ImageUpdateAutomation resources in YAML format\",\n\tLong:  \"The export image update command exports one or all ImageUpdateAutomation resources in YAML format.\",\n\tExample: `  # Export all ImageUpdateAutomation resources\n  flux export image update --all > updates.yaml\n\n  # Export a specific automation\n  flux export image update latest-images > latest.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),\n\tRunE: exportCommand{\n\t\tobject: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},\n\t\tlist:   imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},\n\t}.run,\n}\n\nfunc init() {\n\texportImageCmd.AddCommand(exportImageUpdateCmd)\n}\n\n// exportImageUpdate returns a value which has extraneous information\n// stripped out.\nfunc exportImageUpdate(item *autov1.ImageUpdateAutomation) interface{} {\n\tgvk := autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)\n\texport := autov1.ImageUpdateAutomation{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        item.Name,\n\t\t\tNamespace:   item.Namespace,\n\t\t\tLabels:      item.Labels,\n\t\t\tAnnotations: item.Annotations,\n\t\t},\n\t\tSpec: item.Spec,\n\t}\n\treturn export\n}\n\nfunc (ex imageUpdateAutomationAdapter) export() interface{} {\n\treturn exportImageUpdate(ex.ImageUpdateAutomation)\n}\n\nfunc (ex imageUpdateAutomationListAdapter) exportItem(i int) interface{} {\n\treturn exportImageUpdate(&ex.ImageUpdateAutomationList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_kustomization.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n)\n\nvar exportKsCmd = &cobra.Command{\n\tUse:     \"kustomization [name]\",\n\tAliases: []string{\"ks\"},\n\tShort:   \"Export Kustomization resources in YAML format\",\n\tLong:    `The export kustomization command exports one or all Kustomization resources in YAML format.`,\n\tExample: `  # Export all Kustomization resources\n  flux export kustomization --all > kustomizations.yaml\n\n  # Export a Kustomization\n  flux export kustomization my-app > kustomization.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),\n\tRunE: exportCommand{\n\t\tobject: kustomizationAdapter{&kustomizev1.Kustomization{}},\n\t\tlist:   kustomizationListAdapter{&kustomizev1.KustomizationList{}},\n\t}.run,\n}\n\nfunc init() {\n\texportCmd.AddCommand(exportKsCmd)\n}\n\nfunc exportKs(kustomization *kustomizev1.Kustomization) interface{} {\n\tgvk := kustomizev1.GroupVersion.WithKind(\"Kustomization\")\n\texport := kustomizev1.Kustomization{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        kustomization.Name,\n\t\t\tNamespace:   kustomization.Namespace,\n\t\t\tLabels:      kustomization.Labels,\n\t\t\tAnnotations: kustomization.Annotations,\n\t\t},\n\t\tSpec: kustomization.Spec,\n\t}\n\n\treturn export\n}\n\nfunc (ex kustomizationAdapter) export() interface{} {\n\treturn exportKs(ex.Kustomization)\n}\n\nfunc (ex kustomizationListAdapter) exportItem(i int) interface{} {\n\treturn exportKs(&ex.KustomizationList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_receiver.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n)\n\nvar exportReceiverCmd = &cobra.Command{\n\tUse:   \"receiver [name]\",\n\tShort: \"Export Receiver resources in YAML format\",\n\tLong:  `The export receiver command exports one or all Receiver resources in YAML format.`,\n\tExample: `  # Export all Receiver resources\n  flux export receiver --all > receivers.yaml\n\n  # Export a Receiver\n  flux export receiver main > main.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),\n\tRunE: exportCommand{\n\t\tlist:   receiverListAdapter{&notificationv1.ReceiverList{}},\n\t\tobject: receiverAdapter{&notificationv1.Receiver{}},\n\t}.run,\n}\n\nfunc init() {\n\texportCmd.AddCommand(exportReceiverCmd)\n}\n\nfunc exportReceiver(receiver *notificationv1.Receiver) interface{} {\n\tgvk := notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)\n\texport := notificationv1.Receiver{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        receiver.Name,\n\t\t\tNamespace:   receiver.Namespace,\n\t\t\tLabels:      receiver.Labels,\n\t\t\tAnnotations: receiver.Annotations,\n\t\t},\n\t\tSpec: receiver.Spec,\n\t}\n\n\treturn export\n}\n\nfunc (ex receiverAdapter) export() interface{} {\n\treturn exportReceiver(ex.Receiver)\n}\n\nfunc (ex receiverListAdapter) exportItem(i int) interface{} {\n\treturn exportReceiver(&ex.ReceiverList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_secret.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\n// exportableWithSecret represents a type that you can fetch from the Kubernetes\n// API, get a secretRef from the spec, then tidy up for serialising.\ntype exportableWithSecret interface {\n\tadapter\n\texportable\n\tsecret() *types.NamespacedName\n}\n\n// exportableWithSecretList represents a type that has a list of values, each of\n// which is exportableWithSecret.\ntype exportableWithSecretList interface {\n\tlistAdapter\n\texportableList\n\tsecretItem(i int) *types.NamespacedName\n}\n\ntype exportWithSecretCommand struct {\n\tobject exportableWithSecret\n\tlist   exportableWithSecretList\n}\n\nfunc (export exportWithSecretCommand) run(cmd *cobra.Command, args []string) error {\n\tif !exportArgs.all && len(args) < 1 {\n\t\treturn fmt.Errorf(\"name is required\")\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif exportArgs.all {\n\t\terr = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(*kubeconfigArgs.Namespace))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif export.list.len() == 0 {\n\t\t\treturn fmt.Errorf(\"no objects found in %s namespace\", *kubeconfigArgs.Namespace)\n\t\t}\n\n\t\tfor i := 0; i < export.list.len(); i++ {\n\t\t\tif err = printExport(export.list.exportItem(i)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif exportSourceWithCred {\n\t\t\t\tif export.list.secretItem(i) != nil {\n\t\t\t\t\tnamespacedName := *export.list.secretItem(i)\n\t\t\t\t\treturn printSecretCredentials(ctx, kubeClient, namespacedName)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tname := args[0]\n\t\tnamespacedName := types.NamespacedName{\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tName:      name,\n\t\t}\n\t\terr = kubeClient.Get(ctx, namespacedName, export.object.asClientObject())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := printExport(export.object.export()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif exportSourceWithCred {\n\t\t\tif export.object.secret() != nil {\n\t\t\t\tnamespacedName := *export.object.secret()\n\t\t\t\treturn printSecretCredentials(ctx, kubeClient, namespacedName)\n\t\t\t}\n\t\t}\n\n\t}\n\treturn nil\n}\n\nfunc printSecretCredentials(ctx context.Context, kubeClient client.Client, nsName types.NamespacedName) error {\n\tvar cred corev1.Secret\n\terr := kubeClient.Get(ctx, nsName, &cred)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to retrieve secret %s, error: %w\", nsName.Name, err)\n\t}\n\n\texported := corev1.Secret{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tAPIVersion: \"v1\",\n\t\t\tKind:       \"Secret\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      nsName.Name,\n\t\t\tNamespace: nsName.Namespace,\n\t\t},\n\t\tData: cred.Data,\n\t\tType: cred.Type,\n\t}\n\treturn printExport(exported)\n}\n"
  },
  {
    "path": "cmd/flux/export_source.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar exportSourceCmd = &cobra.Command{\n\tUse:   \"source\",\n\tShort: \"Export sources\",\n\tLong:  `The export source sub-commands export sources in YAML format.`,\n}\n\nvar (\n\texportSourceWithCred bool\n)\n\nfunc init() {\n\texportSourceCmd.PersistentFlags().BoolVar(&exportSourceWithCred, \"with-credentials\", false, \"include credential secrets\")\n\n\texportCmd.AddCommand(exportSourceCmd)\n}\n"
  },
  {
    "path": "cmd/flux/export_source_bucket.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar exportSourceBucketCmd = &cobra.Command{\n\tUse:   \"bucket [name]\",\n\tShort: \"Export Bucket sources in YAML format\",\n\tLong:  \"The export source git command exports one or all Bucket sources in YAML format.\",\n\tExample: `  # Export all Bucket sources\n  flux export source bucket --all > sources.yaml\n\n  # Export a Bucket source including the static credentials\n  flux export source bucket my-bucket --with-credentials > source.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),\n\tRunE: exportWithSecretCommand{\n\t\tlist:   bucketListAdapter{&sourcev1.BucketList{}},\n\t\tobject: bucketAdapter{&sourcev1.Bucket{}},\n\t}.run,\n}\n\nfunc init() {\n\texportSourceCmd.AddCommand(exportSourceBucketCmd)\n}\n\nfunc exportBucket(source *sourcev1.Bucket) interface{} {\n\tgvk := sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)\n\texport := sourcev1.Bucket{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        source.Name,\n\t\t\tNamespace:   source.Namespace,\n\t\t\tLabels:      source.Labels,\n\t\t\tAnnotations: source.Annotations,\n\t\t},\n\t\tSpec: source.Spec,\n\t}\n\treturn export\n}\n\nfunc getBucketSecret(source *sourcev1.Bucket) *types.NamespacedName {\n\tif source.Spec.SecretRef != nil {\n\t\tnamespacedName := types.NamespacedName{\n\t\t\tNamespace: source.Namespace,\n\t\t\tName:      source.Spec.SecretRef.Name,\n\t\t}\n\n\t\treturn &namespacedName\n\t}\n\n\treturn nil\n}\n\nfunc (ex bucketAdapter) secret() *types.NamespacedName {\n\treturn getBucketSecret(ex.Bucket)\n}\n\nfunc (ex bucketListAdapter) secretItem(i int) *types.NamespacedName {\n\treturn getBucketSecret(&ex.BucketList.Items[i])\n}\n\nfunc (ex bucketAdapter) export() interface{} {\n\treturn exportBucket(ex.Bucket)\n}\n\nfunc (ex bucketListAdapter) exportItem(i int) interface{} {\n\treturn exportBucket(&ex.BucketList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_source_chart.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar exportSourceChartCmd = &cobra.Command{\n\tUse:   \"chart [name]\",\n\tShort: \"Export HelmChart sources in YAML format\",\n\tLong:  withPreviewNote(\"The export source chart command exports one or all HelmChart sources in YAML format.\"),\n\tExample: `  # Export all chart sources\n  flux export source chart --all > sources.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),\n\tRunE: exportCommand{\n\t\tlist:   helmChartListAdapter{&sourcev1.HelmChartList{}},\n\t\tobject: helmChartAdapter{&sourcev1.HelmChart{}},\n\t}.run,\n}\n\nfunc init() {\n\texportSourceCmd.AddCommand(exportSourceChartCmd)\n}\n\nfunc exportHelmChart(source *sourcev1.HelmChart) interface{} {\n\tgvk := sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)\n\texport := sourcev1.HelmChart{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        source.Name,\n\t\t\tNamespace:   source.Namespace,\n\t\t\tLabels:      source.Labels,\n\t\t\tAnnotations: source.Annotations,\n\t\t},\n\t\tSpec: source.Spec,\n\t}\n\treturn export\n}\n\nfunc (ex helmChartAdapter) export() interface{} {\n\treturn exportHelmChart(ex.HelmChart)\n}\n\nfunc (ex helmChartListAdapter) exportItem(i int) interface{} {\n\treturn exportHelmChart(&ex.HelmChartList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_source_external.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar exportSourceExternalCmd = &cobra.Command{\n\tUse:   \"external [name]\",\n\tShort: \"Export ExternalArtifact sources in YAML format\",\n\tLong:  \"The export source external command exports one or all ExternalArtifact sources in YAML format.\",\n\tExample: `  # Export all ExternalArtifact sources\n  flux export source external --all > sources.yaml\n\n  # Export a specific ExternalArtifact\n  flux export source external my-artifact > source.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.ExternalArtifactKind)),\n\tRunE: exportWithSecretCommand{\n\t\tlist:   externalArtifactListAdapter{&sourcev1.ExternalArtifactList{}},\n\t\tobject: externalArtifactAdapter{&sourcev1.ExternalArtifact{}},\n\t}.run,\n}\n\nfunc init() {\n\texportSourceCmd.AddCommand(exportSourceExternalCmd)\n}\n\nfunc exportExternalArtifact(source *sourcev1.ExternalArtifact) any {\n\tgvk := sourcev1.GroupVersion.WithKind(sourcev1.ExternalArtifactKind)\n\texport := sourcev1.ExternalArtifact{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        source.Name,\n\t\t\tNamespace:   source.Namespace,\n\t\t\tLabels:      source.Labels,\n\t\t\tAnnotations: source.Annotations,\n\t\t},\n\t\tSpec: source.Spec,\n\t}\n\treturn export\n}\n\nfunc getExternalArtifactSecret(source *sourcev1.ExternalArtifact) *types.NamespacedName {\n\t// ExternalArtifact does not have a secretRef in its spec, this satisfies the interface\n\treturn nil\n}\n\nfunc (ex externalArtifactAdapter) secret() *types.NamespacedName {\n\treturn getExternalArtifactSecret(ex.ExternalArtifact)\n}\n\nfunc (ex externalArtifactListAdapter) secretItem(i int) *types.NamespacedName {\n\treturn getExternalArtifactSecret(&ex.ExternalArtifactList.Items[i])\n}\n\nfunc (ex externalArtifactAdapter) export() any {\n\treturn exportExternalArtifact(ex.ExternalArtifact)\n}\n\nfunc (ex externalArtifactListAdapter) exportItem(i int) any {\n\treturn exportExternalArtifact(&ex.ExternalArtifactList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_source_git.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar exportSourceGitCmd = &cobra.Command{\n\tUse:   \"git [name]\",\n\tShort: \"Export GitRepository sources in YAML format\",\n\tLong:  `The export source git command exports one or all GitRepository sources in YAML format.`,\n\tExample: `  # Export all GitRepository sources\n  flux export source git --all > sources.yaml\n\n  # Export a GitRepository source including the SSH key pair or basic auth credentials\n  flux export source git my-private-repo --with-credentials > source.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),\n\tRunE: exportWithSecretCommand{\n\t\tobject: gitRepositoryAdapter{&sourcev1.GitRepository{}},\n\t\tlist:   gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},\n\t}.run,\n}\n\nfunc init() {\n\texportSourceCmd.AddCommand(exportSourceGitCmd)\n}\n\nfunc exportGit(source *sourcev1.GitRepository) interface{} {\n\tgvk := sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)\n\texport := sourcev1.GitRepository{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        source.Name,\n\t\t\tNamespace:   source.Namespace,\n\t\t\tLabels:      source.Labels,\n\t\t\tAnnotations: source.Annotations,\n\t\t},\n\t\tSpec: source.Spec,\n\t}\n\n\treturn export\n}\n\nfunc getGitSecret(source *sourcev1.GitRepository) *types.NamespacedName {\n\tif source.Spec.SecretRef != nil {\n\t\tnamespacedName := types.NamespacedName{\n\t\t\tNamespace: source.Namespace,\n\t\t\tName:      source.Spec.SecretRef.Name,\n\t\t}\n\t\treturn &namespacedName\n\t}\n\n\treturn nil\n}\n\nfunc (ex gitRepositoryAdapter) secret() *types.NamespacedName {\n\treturn getGitSecret(ex.GitRepository)\n}\n\nfunc (ex gitRepositoryListAdapter) secretItem(i int) *types.NamespacedName {\n\treturn getGitSecret(&ex.GitRepositoryList.Items[i])\n}\n\nfunc (ex gitRepositoryAdapter) export() interface{} {\n\treturn exportGit(ex.GitRepository)\n}\n\nfunc (ex gitRepositoryListAdapter) exportItem(i int) interface{} {\n\treturn exportGit(&ex.GitRepositoryList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_source_helm.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar exportSourceHelmCmd = &cobra.Command{\n\tUse:   \"helm [name]\",\n\tShort: \"Export HelmRepository sources in YAML format\",\n\tLong:  \"The export source git command exports one or all HelmRepository sources in YAML format.\",\n\tExample: `  # Export all HelmRepository sources\n  flux export source helm --all > sources.yaml\n\n  # Export a HelmRepository source including the basic auth credentials\n  flux export source helm my-private-repo --with-credentials > source.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),\n\tRunE: exportWithSecretCommand{\n\t\tlist:   helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},\n\t\tobject: helmRepositoryAdapter{&sourcev1.HelmRepository{}},\n\t}.run,\n}\n\nfunc init() {\n\texportSourceCmd.AddCommand(exportSourceHelmCmd)\n}\n\nfunc exportHelmRepository(source *sourcev1.HelmRepository) interface{} {\n\tgvk := sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)\n\texport := sourcev1.HelmRepository{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        source.Name,\n\t\t\tNamespace:   source.Namespace,\n\t\t\tLabels:      source.Labels,\n\t\t\tAnnotations: source.Annotations,\n\t\t},\n\t\tSpec: source.Spec,\n\t}\n\treturn export\n}\n\nfunc getHelmSecret(source *sourcev1.HelmRepository) *types.NamespacedName {\n\tif source.Spec.SecretRef != nil {\n\t\tnamespacedName := types.NamespacedName{\n\t\t\tNamespace: source.Namespace,\n\t\t\tName:      source.Spec.SecretRef.Name,\n\t\t}\n\t\treturn &namespacedName\n\t}\n\treturn nil\n}\n\nfunc (ex helmRepositoryAdapter) secret() *types.NamespacedName {\n\treturn getHelmSecret(ex.HelmRepository)\n}\n\nfunc (ex helmRepositoryListAdapter) secretItem(i int) *types.NamespacedName {\n\treturn getHelmSecret(&ex.HelmRepositoryList.Items[i])\n}\n\nfunc (ex helmRepositoryAdapter) export() interface{} {\n\treturn exportHelmRepository(ex.HelmRepository)\n}\n\nfunc (ex helmRepositoryListAdapter) exportItem(i int) interface{} {\n\treturn exportHelmRepository(&ex.HelmRepositoryList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_source_oci.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar exportSourceOCIRepositoryCmd = &cobra.Command{\n\tUse:   \"oci [name]\",\n\tShort: \"Export OCIRepository sources in YAML format\",\n\tLong:  withPreviewNote(\"The export source oci command exports one or all OCIRepository sources in YAML format.\"),\n\tExample: `  # Export all OCIRepository sources\n  flux export source oci --all > sources.yaml\n\n  # Export a OCIRepository including the static credentials\n  flux export source oci my-app --with-credentials > source.yaml`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),\n\tRunE: exportWithSecretCommand{\n\t\tlist:   ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},\n\t\tobject: ociRepositoryAdapter{&sourcev1.OCIRepository{}},\n\t}.run,\n}\n\nfunc init() {\n\texportSourceCmd.AddCommand(exportSourceOCIRepositoryCmd)\n}\n\nfunc exportOCIRepository(source *sourcev1.OCIRepository) interface{} {\n\tgvk := sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)\n\texport := sourcev1.OCIRepository{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        source.Name,\n\t\t\tNamespace:   source.Namespace,\n\t\t\tLabels:      source.Labels,\n\t\t\tAnnotations: source.Annotations,\n\t\t},\n\t\tSpec: source.Spec,\n\t}\n\treturn export\n}\n\nfunc getOCIRepositorySecret(source *sourcev1.OCIRepository) *types.NamespacedName {\n\tif source.Spec.SecretRef != nil {\n\t\tnamespacedName := types.NamespacedName{\n\t\t\tNamespace: source.Namespace,\n\t\t\tName:      source.Spec.SecretRef.Name,\n\t\t}\n\n\t\treturn &namespacedName\n\t}\n\n\treturn nil\n}\n\nfunc (ex ociRepositoryAdapter) secret() *types.NamespacedName {\n\treturn getOCIRepositorySecret(ex.OCIRepository)\n}\n\nfunc (ex ociRepositoryListAdapter) secretItem(i int) *types.NamespacedName {\n\treturn getOCIRepositorySecret(&ex.OCIRepositoryList.Items[i])\n}\n\nfunc (ex ociRepositoryAdapter) export() interface{} {\n\treturn exportOCIRepository(ex.OCIRepository)\n}\n\nfunc (ex ociRepositoryListAdapter) exportItem(i int) interface{} {\n\treturn exportOCIRepository(&ex.OCIRepositoryList.Items[i])\n}\n"
  },
  {
    "path": "cmd/flux/export_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestExport(t *testing.T) {\n\tnamespace := allocateNamespace(\"flux-system\")\n\n\tobjectFile := \"testdata/export/objects.yaml\"\n\ttmpl := map[string]string{\n\t\t\"fluxns\": namespace,\n\t}\n\ttestEnv.CreateObjectFile(objectFile, tmpl, t)\n\n\tcases := []struct {\n\t\tname       string\n\t\targ        string\n\t\tgoldenFile string\n\t\ttmpl       map[string]string\n\t}{\n\t\t{\n\t\t\t\"alert-provider\",\n\t\t\t\"export alert-provider slack\",\n\t\t\t\"testdata/export/provider.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"alert\",\n\t\t\t\"export alert flux-system\",\n\t\t\t\"testdata/export/alert.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"image policy\",\n\t\t\t\"export image policy flux-system\",\n\t\t\t\"testdata/export/image-policy.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"image repository\",\n\t\t\t\"export image repository flux-system\",\n\t\t\t\"testdata/export/image-repo.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"image update\",\n\t\t\t\"export image update flux-system\",\n\t\t\t\"testdata/export/image-update.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"source git\",\n\t\t\t\"export source git flux-system\",\n\t\t\t\"testdata/export/git-repo.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"source chart\",\n\t\t\t\"export source chart flux-system\",\n\t\t\t\"testdata/export/helm-chart.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"source helm\",\n\t\t\t\"export source helm flux-system\",\n\t\t\t\"testdata/export/helm-repo.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"receiver\",\n\t\t\t\"export receiver flux-system\",\n\t\t\t\"testdata/export/receiver.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"kustomization\",\n\t\t\t\"export kustomization flux-system\",\n\t\t\t\"testdata/export/ks.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"helmrelease\",\n\t\t\t\"export helmrelease flux-system\",\n\t\t\t\"testdata/export/helm-release.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"bucket\",\n\t\t\t\"export source bucket flux-system\",\n\t\t\t\"testdata/export/bucket.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"source external\",\n\t\t\t\"export source external flux-system\",\n\t\t\t\"testdata/export/external-artifact.yaml\",\n\t\t\ttmpl,\n\t\t},\n\t}\n\n\tfor _, tt := range cases {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.arg + \" -n=\" + namespace,\n\t\t\t\tassert: assertGoldenTemplateFile(tt.goldenFile, tmpl),\n\t\t\t}\n\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/get.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\tapimeta \"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\twatchtools \"k8s.io/client-go/tools/watch\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/printers\"\n)\n\ntype deriveType func(runtime.Object) (summarisable, error)\n\ntype typeMap map[string]deriveType\n\nfunc (m typeMap) registerCommand(t string, f deriveType) error {\n\tif _, ok := m[t]; ok {\n\t\treturn fmt.Errorf(\"duplicate type function %s\", t)\n\t}\n\tm[t] = f\n\treturn nil\n}\n\nfunc (m typeMap) execute(t string, obj runtime.Object) (summarisable, error) {\n\tf, ok := m[t]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unsupported type %s\", t)\n\t}\n\treturn f(obj)\n}\n\nvar getCmd = &cobra.Command{\n\tUse:   \"get\",\n\tShort: \"Get the resources and their status\",\n\tLong:  `The get sub-commands print the statuses of Flux resources.`,\n}\n\ntype GetFlags struct {\n\tallNamespaces  bool\n\tnoHeader       bool\n\tstatusSelector string\n\tlabelSelector  string\n\twatch          bool\n}\n\nvar getArgs GetFlags\n\nfunc init() {\n\tgetCmd.PersistentFlags().BoolVarP(&getArgs.allNamespaces, \"all-namespaces\", \"A\", false,\n\t\t\"list the requested object(s) across all namespaces\")\n\tgetCmd.PersistentFlags().BoolVarP(&getArgs.noHeader, \"no-header\", \"\", false, \"skip the header when printing the results\")\n\tgetCmd.PersistentFlags().BoolVarP(&getArgs.watch, \"watch\", \"w\", false, \"After listing/getting the requested object, watch for changes.\")\n\tgetCmd.PersistentFlags().StringVar(&getArgs.statusSelector, \"status-selector\", \"\",\n\t\t\"specify the status condition name and the desired state to filter the get result, e.g. ready=false\")\n\tgetCmd.PersistentFlags().StringVarP(&getArgs.labelSelector, \"label-selector\", \"l\", \"\",\n\t\t\"filter objects by label selector\")\n\trootCmd.AddCommand(getCmd)\n}\n\ntype summarisable interface {\n\tlistAdapter\n\tsummariseItem(i int, includeNamespace bool, includeKind bool) []string\n\theaders(includeNamespace bool) []string\n\tstatusSelectorMatches(i int, conditionType, conditionStatus string) bool\n}\n\n// --- these help with implementations of summarisable\n\nfunc statusAndMessage(conditions []metav1.Condition) (string, string) {\n\tif c := apimeta.FindStatusCondition(conditions, meta.ReadyCondition); c != nil {\n\t\treturn string(c.Status), c.Message\n\t}\n\treturn string(metav1.ConditionFalse), \"waiting to be reconciled\"\n}\n\nfunc statusMatches(conditionType, conditionStatus string, conditions []metav1.Condition) bool {\n\t// we don't use apimeta.FindStatusCondition because we'd like to use EqualFold to compare two strings\n\tvar c *metav1.Condition\n\tfor i := range conditions {\n\t\tif strings.EqualFold(conditions[i].Type, conditionType) {\n\t\t\tc = &conditions[i]\n\t\t}\n\t}\n\tif c != nil {\n\t\treturn strings.EqualFold(string(c.Status), conditionStatus)\n\t}\n\treturn false\n}\n\nfunc nameColumns(item named, includeNamespace bool, includeKind bool) []string {\n\tname := item.GetName()\n\tif includeKind {\n\t\tname = fmt.Sprintf(\"%s/%s\",\n\t\t\tstrings.ToLower(item.GetObjectKind().GroupVersionKind().Kind),\n\t\t\titem.GetName())\n\t}\n\tif includeNamespace {\n\t\treturn []string{item.GetNamespace(), name}\n\t}\n\treturn []string{name}\n}\n\nvar namespaceHeader = []string{\"Namespace\"}\n\ntype getCommand struct {\n\tapiType\n\tlist    summarisable\n\tfuncMap typeMap\n}\n\nfunc (get getCommand) run(cmd *cobra.Command, args []string) error {\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar listOpts []client.ListOption\n\tif !getArgs.allNamespaces {\n\t\tlistOpts = append(listOpts, client.InNamespace(*kubeconfigArgs.Namespace))\n\t}\n\n\tif len(args) > 0 {\n\t\tlistOpts = append(listOpts, client.MatchingFields{\"metadata.name\": args[0]})\n\t}\n\n\tif getArgs.labelSelector != \"\" {\n\t\tlabel, err := metav1.ParseToLabelSelector(getArgs.labelSelector)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to parse label selector: %w\", err)\n\t\t}\n\n\t\tsel, err := metav1.LabelSelectorAsSelector(label)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlistOpts = append(listOpts, client.MatchingLabelsSelector{\n\t\t\tSelector: sel,\n\t\t})\n\t}\n\n\tgetAll := cmd.Use == \"all\"\n\n\tif getArgs.watch {\n\t\treturn get.watch(ctx, kubeClient, cmd, args, listOpts)\n\t}\n\n\terr = kubeClient.List(ctx, get.list.asClientList(), listOpts...)\n\tif err != nil {\n\t\tif getAll && apimeta.IsNoMatchError(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\n\tif get.list.len() == 0 {\n\t\tif len(args) > 0 {\n\t\t\treturn fmt.Errorf(\"%s object '%s' not found in %s namespace\",\n\t\t\t\tget.kind,\n\t\t\t\targs[0],\n\t\t\t\tnamespaceNameOrAny(getArgs.allNamespaces, *kubeconfigArgs.Namespace),\n\t\t\t)\n\t\t} else if !getAll {\n\t\t\treturn fmt.Errorf(\"no %s objects found in %s namespace\",\n\t\t\t\tget.kind,\n\t\t\t\tnamespaceNameOrAny(getArgs.allNamespaces, *kubeconfigArgs.Namespace),\n\t\t\t)\n\t\t}\n\t\treturn nil\n\t}\n\n\tvar header []string\n\tif !getArgs.noHeader {\n\t\theader = get.list.headers(getArgs.allNamespaces)\n\t}\n\n\trows, err := getRowsToPrint(getAll, get.list)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif getAll {\n\t\tfmt.Println()\n\t}\n\n\treturn nil\n}\n\nfunc namespaceNameOrAny(allNamespaces bool, namespaceName string) string {\n\tif allNamespaces {\n\t\treturn \"any\"\n\t}\n\treturn fmt.Sprintf(\"%q\", namespaceName)\n}\n\nfunc getRowsToPrint(getAll bool, list summarisable) ([][]string, error) {\n\tnoFilter := true\n\tvar conditionType, conditionStatus string\n\tif getArgs.statusSelector != \"\" {\n\t\tparts := strings.SplitN(getArgs.statusSelector, \"=\", 2)\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, fmt.Errorf(\"expected status selector in type=status format, but found: %s\", getArgs.statusSelector)\n\t\t}\n\t\tconditionType = parts[0]\n\t\tconditionStatus = parts[1]\n\t\tnoFilter = false\n\t}\n\tvar rows [][]string\n\tfor i := 0; i < list.len(); i++ {\n\t\tif noFilter || list.statusSelectorMatches(i, conditionType, conditionStatus) {\n\t\t\trow := list.summariseItem(i, getArgs.allNamespaces, getAll)\n\t\t\trows = append(rows, row)\n\t\t}\n\t}\n\treturn rows, nil\n}\n\n// watch starts a client-side watch of one or more resources.\nfunc (get *getCommand) watch(ctx context.Context, kubeClient client.WithWatch, cmd *cobra.Command, args []string, listOpts []client.ListOption) error {\n\tw, err := kubeClient.Watch(ctx, get.list.asClientList(), listOpts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = watchUntil(ctx, w, get)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc watchUntil(ctx context.Context, w watch.Interface, get *getCommand) (bool, error) {\n\tfirstIteration := true\n\t_, error := watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) {\n\t\tobjToPrint := e.Object\n\t\tsink, err := get.funcMap.execute(get.apiType.kind, objToPrint)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tvar header []string\n\t\tif !getArgs.noHeader {\n\t\t\theader = sink.headers(getArgs.allNamespaces)\n\t\t}\n\t\trows, err := getRowsToPrint(false, sink)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif firstIteration {\n\t\t\terr = printers.TablePrinter(header).Print(os.Stdout, rows)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tfirstIteration = false\n\t\t} else {\n\t\t\terr = printers.TablePrinter([]string{}).Print(os.Stdout, rows)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\n\t\treturn false, nil\n\t})\n\n\treturn false, error\n}\n\nfunc validateWatchOption(cmd *cobra.Command, toMatch string) error {\n\tw, _ := cmd.Flags().GetBool(\"watch\")\n\tif cmd.Use == toMatch && w {\n\t\treturn fmt.Errorf(\"expected a single resource type, but found %s\", cmd.Use)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/get_alert.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\nvar getAlertCmd = &cobra.Command{\n\tUse:     \"alerts\",\n\tAliases: []string{\"alert\"},\n\tShort:   \"Get Alert statuses\",\n\tLong:    withPreviewNote(\"The get alert command prints the statuses of the resources.\"),\n\tExample: `  # List all Alerts and their status\n  flux get alerts`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: alertType,\n\t\t\tlist:    &alertListAdapter{&notificationv1.AlertList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*notificationv1.Alert)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v alert\", obj)\n\t\t\t}\n\n\t\t\tsink := alertListAdapter{\n\t\t\t\t&notificationv1.AlertList{\n\t\t\t\t\tItems: []notificationv1.Alert{\n\t\t\t\t\t\t*o,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetCmd.AddCommand(getAlertCmd)\n}\n\nfunc (s alertListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := s.Items[i]\n\tstatus, msg := string(metav1.ConditionTrue), \"Alert is Ready\"\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\tcases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)\n}\n\nfunc (s alertListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\treturn append(namespaceHeader, headers...)\n\t}\n\treturn headers\n}\n\nfunc (s alertListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/get_alertprovider.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\nvar getAlertProviderCmd = &cobra.Command{\n\tUse:     \"alert-providers\",\n\tAliases: []string{\"alert-provider\"},\n\tShort:   \"Get Provider statuses\",\n\tLong:    withPreviewNote(\"The get alert-provider command prints the statuses of the resources.\"),\n\tExample: `  # List all Providers and their status\n  flux get alert-providers`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: alertProviderType,\n\t\t\tlist:    alertProviderListAdapter{&notificationv1.ProviderList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*notificationv1.Provider)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v alert-provider\", obj)\n\t\t\t}\n\n\t\t\tsink := alertProviderListAdapter{\n\t\t\t\t&notificationv1.ProviderList{\n\t\t\t\t\tItems: []notificationv1.Provider{\n\t\t\t\t\t\t*o,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetCmd.AddCommand(getAlertProviderCmd)\n}\n\nfunc (s alertProviderListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := s.Items[i]\n\tstatus, msg := string(metav1.ConditionTrue), \"Provider is Ready\"\n\treturn append(nameColumns(&item, includeNamespace, includeKind), status, msg)\n}\n\nfunc (s alertProviderListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\treturn append(namespaceHeader, headers...)\n\t}\n\treturn headers\n}\n\nfunc (s alertProviderListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/get_all.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tapimeta \"k8s.io/apimachinery/pkg/api/meta\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n\tnotificationv1b3 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\nvar getAllCmd = &cobra.Command{\n\tUse:   \"all\",\n\tShort: \"Get all resources and statuses\",\n\tLong:  withPreviewNote(\"The get all command print the statuses of all resources.\"),\n\tExample: `  # List all resources in a namespace\n  flux get all --namespace=flux-system\n\n  # List all resources in all namespaces\n  flux get all --all-namespaces`,\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\terr := validateWatchOption(cmd, \"all\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = getSourceAllCmd.RunE(cmd, args)\n\t\tif err != nil {\n\t\t\tlogError(err)\n\t\t}\n\n\t\t// all get command\n\t\tvar allCmd = []getCommand{\n\t\t\t{\n\t\t\t\tapiType: helmReleaseType,\n\t\t\t\tlist:    &helmReleaseListAdapter{&helmv2.HelmReleaseList{}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tapiType: kustomizationType,\n\t\t\t\tlist:    &kustomizationListAdapter{&kustomizev1.KustomizationList{}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tapiType: receiverType,\n\t\t\t\tlist:    receiverListAdapter{&notificationv1.ReceiverList{}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tapiType: alertProviderType,\n\t\t\t\tlist:    alertProviderListAdapter{&notificationv1b3.ProviderList{}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tapiType: alertType,\n\t\t\t\tlist:    &alertListAdapter{&notificationv1b3.AlertList{}},\n\t\t\t},\n\t\t}\n\n\t\terr = getImageAllCmd.RunE(cmd, args)\n\t\tif err != nil {\n\t\t\tlogError(err)\n\t\t}\n\n\t\tfor _, c := range allCmd {\n\t\t\tif err := c.run(cmd, args); err != nil {\n\t\t\t\tlogError(err)\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc logError(err error) {\n\tif !apimeta.IsNoMatchError(err) {\n\t\tlogger.Failuref(\"%s\", err.Error())\n\t}\n}\n\nfunc init() {\n\tgetCmd.AddCommand(getAllCmd)\n}\n"
  },
  {
    "path": "cmd/flux/get_artifact.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar getArtifactCmd = &cobra.Command{\n\tUse:     \"artifacts\",\n\tAliases: []string{\"artifact\"},\n\tShort:   \"Get artifact object status\",\n\tLong:    `The get artifact sub-commands print the status of artifact objects.`,\n}\n\nfunc init() {\n\tgetCmd.AddCommand(getArtifactCmd)\n}\n"
  },
  {
    "path": "cmd/flux/get_artifact_generator.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tswapi \"github.com/fluxcd/source-watcher/api/v2/v1beta1\"\n)\n\nvar getArtifactGeneratorCmd = &cobra.Command{\n\tUse:     \"generators\",\n\tAliases: []string{\"generator\"},\n\tShort:   \"Get artifact generator statuses\",\n\tLong:    `The get artifact generator command prints the statuses of the resources.`,\n\tExample: `  # List all ArtifactGenerators and their status\n  flux get artifact generators`,\n\tValidArgsFunction: resourceNamesCompletionFunc(swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: receiverType,\n\t\t\tlist:    artifactGeneratorListAdapter{&swapi.ArtifactGeneratorList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*swapi.ArtifactGenerator)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"impossible to cast type %#v generator\", obj)\n\t\t\t}\n\n\t\t\tsink := artifactGeneratorListAdapter{&swapi.ArtifactGeneratorList{\n\t\t\t\tItems: []swapi.ArtifactGenerator{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetArtifactCmd.AddCommand(getArtifactGeneratorCmd)\n}\n\nfunc (s artifactGeneratorListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := s.Items[i]\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\tcases.Title(language.English).String(strconv.FormatBool(item.IsDisabled())), status, msg)\n}\n\nfunc (s artifactGeneratorListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\treturn append(namespaceHeader, headers...)\n\t}\n\treturn headers\n}\n\nfunc (s artifactGeneratorListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := s.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_helmrelease.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n)\n\nvar getHelmReleaseCmd = &cobra.Command{\n\tUse:     \"helmreleases\",\n\tAliases: []string{\"hr\", \"helmrelease\"},\n\tShort:   \"Get HelmRelease statuses\",\n\tLong:    \"The get helmreleases command prints the statuses of the resources.\",\n\tExample: `  # List all Helm releases and their status\n  flux get helmreleases`,\n\tValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: helmReleaseType,\n\t\t\tlist:    &helmReleaseListAdapter{&helmv2.HelmReleaseList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*helmv2.HelmRelease)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v helmrelease\", obj)\n\t\t\t}\n\n\t\t\tsink := helmReleaseListAdapter{&helmv2.HelmReleaseList{\n\t\t\t\tItems: []helmv2.HelmRelease{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetCmd.AddCommand(getHelmReleaseCmd)\n}\n\nfunc getHelmReleaseRevision(helmRelease helmv2.HelmRelease) string {\n\tif helmRelease.Status.History != nil && len(helmRelease.Status.History) > 0 {\n\t\treturn helmRelease.Status.History[0].ChartVersion\n\t}\n\treturn helmRelease.Status.LastAttemptedRevision\n}\n\nfunc (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := a.Items[i]\n\trevision := getHelmReleaseRevision(item)\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\trevision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)\n}\n\nfunc (a helmReleaseListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Revision\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\theaders = append([]string{\"Namespace\"}, headers...)\n\t}\n\treturn headers\n}\n\nfunc (a helmReleaseListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := a.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_image.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar getImageCmd = &cobra.Command{\n\tUse:     \"images\",\n\tAliases: []string{\"image\"},\n\tShort:   \"Get image automation object status\",\n\tLong:    `The get image sub-commands print the status of image automation objects.`,\n}\n\nfunc init() {\n\tgetCmd.AddCommand(getImageCmd)\n}\n"
  },
  {
    "path": "cmd/flux/get_image_all.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar getImageAllCmd = &cobra.Command{\n\tUse:   \"all\",\n\tShort: \"Get all image statuses\",\n\tLong:  \"The get image sub-commands print the statuses of all image objects.\",\n\tExample: `  # List all image objects in a namespace\n  flux get images all --namespace=flux-system\n\n  # List all image objects in all namespaces\n  flux get images all --all-namespaces`,\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\terr := validateWatchOption(cmd, \"all\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar allImageCmd = []getCommand{\n\t\t\t{\n\t\t\t\tapiType: imageRepositoryType,\n\t\t\t\tlist:    imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tapiType: imagePolicyType,\n\t\t\t\tlist:    &imagePolicyListAdapter{&imagev1.ImagePolicyList{}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tapiType: imageUpdateAutomationType,\n\t\t\t\tlist:    &imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},\n\t\t\t},\n\t\t}\n\n\t\tfor _, c := range allImageCmd {\n\t\t\tif err := c.run(cmd, args); err != nil {\n\t\t\t\tlogger.Failuref(\"%s\", err.Error())\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetImageCmd.AddCommand(getImageAllCmd)\n}\n"
  },
  {
    "path": "cmd/flux/get_image_policy.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar getImagePolicyCmd = &cobra.Command{\n\tUse:   \"policy\",\n\tShort: \"Get ImagePolicy status\",\n\tLong:  \"The get image policy command prints the status of ImagePolicy objects.\",\n\tExample: `  # List all image policies and their status\n  flux get image policy\n\n # List image policies from all namespaces\n  flux get image policy --all-namespaces`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: imagePolicyType,\n\t\t\tlist:    &imagePolicyListAdapter{&imagev1.ImagePolicyList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*imagev1.ImagePolicy)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v policy\", obj)\n\t\t\t}\n\n\t\t\tsink := imagePolicyListAdapter{&imagev1.ImagePolicyList{\n\t\t\t\tItems: []imagev1.ImagePolicy{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetImageCmd.AddCommand(getImagePolicyCmd)\n}\n\nfunc (s imagePolicyListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := s.Items[i]\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\tvar image, tag string\n\tif ref := item.Status.LatestRef; ref != nil {\n\t\timage = ref.Name\n\t\ttag = ref.Tag\n\t}\n\treturn append(nameColumns(&item, includeNamespace, includeKind), image, tag, status, msg)\n}\n\nfunc (s imagePolicyListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Image\", \"Tag\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\treturn append(namespaceHeader, headers...)\n\t}\n\treturn headers\n}\n\nfunc (s imagePolicyListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := s.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_image_repository.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar getImageRepositoryCmd = &cobra.Command{\n\tUse:   \"repository\",\n\tShort: \"Get ImageRepository status\",\n\tLong:  \"The get image repository command prints the status of ImageRepository objects.\",\n\tExample: `  # List all image repositories and their status\n  flux get image repository\n\n # List image repositories from all namespaces\n  flux get image repository --all-namespaces`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: imageRepositoryType,\n\t\t\tlist:    imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*imagev1.ImageRepository)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v repository\", obj)\n\t\t\t}\n\n\t\t\tsink := imageRepositoryListAdapter{&imagev1.ImageRepositoryList{\n\t\t\t\tItems: []imagev1.ImageRepository{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetImageCmd.AddCommand(getImageRepositoryCmd)\n}\n\nfunc (s imageRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := s.Items[i]\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\tvar lastScan string\n\tif item.Status.LastScanResult != nil {\n\t\tlastScan = item.Status.LastScanResult.ScanTime.Time.Format(time.RFC3339)\n\t}\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\tlastScan, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)\n}\n\nfunc (s imageRepositoryListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Last scan\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\treturn append(namespaceHeader, headers...)\n\t}\n\treturn headers\n}\n\nfunc (s imageRepositoryListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := s.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_image_update.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n)\n\nvar getImageUpdateCmd = &cobra.Command{\n\tUse:   \"update\",\n\tShort: \"Get ImageUpdateAutomation status\",\n\tLong:  \"The get image update command prints the status of ImageUpdateAutomation objects.\",\n\tExample: `  # List all image update automation object and their status\n  flux get image update\n\n # List image update automations from all namespaces\n  flux get image update --all-namespaces`,\n\tValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: imageUpdateAutomationType,\n\t\t\tlist:    &imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*autov1.ImageUpdateAutomation)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v update\", obj)\n\t\t\t}\n\n\t\t\tsink := imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{\n\t\t\t\tItems: []autov1.ImageUpdateAutomation{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetImageCmd.AddCommand(getImageUpdateCmd)\n}\n\nfunc (s imageUpdateAutomationListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := s.Items[i]\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\tvar lastRun string\n\tif item.Status.LastAutomationRunTime != nil {\n\t\tlastRun = item.Status.LastAutomationRunTime.Time.Format(time.RFC3339)\n\t}\n\treturn append(nameColumns(&item, includeNamespace, includeKind), lastRun,\n\t\tcases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)\n}\n\nfunc (s imageUpdateAutomationListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Last run\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\treturn append(namespaceHeader, headers...)\n\t}\n\treturn headers\n}\n\nfunc (s imageUpdateAutomationListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := s.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_kustomization.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar getKsCmd = &cobra.Command{\n\tUse:     \"kustomizations\",\n\tAliases: []string{\"ks\", \"kustomization\"},\n\tShort:   \"Get Kustomization statuses\",\n\tLong:    `The get kustomizations command prints the statuses of the resources.`,\n\tExample: `  # List all kustomizations and their status\n  flux get kustomizations`,\n\tValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: kustomizationType,\n\t\t\tlist:    &kustomizationListAdapter{&kustomizev1.KustomizationList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*kustomizev1.Kustomization)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v kustomization\", obj)\n\t\t\t}\n\n\t\t\tsink := kustomizationListAdapter{\n\t\t\t\t&kustomizev1.KustomizationList{\n\t\t\t\t\tItems: []kustomizev1.Kustomization{\n\t\t\t\t\t\t*o,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetCmd.AddCommand(getKsCmd)\n}\n\nfunc (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := a.Items[i]\n\trevision := item.Status.LastAppliedRevision\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\trevision = utils.TruncateHex(revision)\n\tmsg = utils.TruncateHex(msg)\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\trevision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)\n}\n\nfunc (a kustomizationListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Revision\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\theaders = append([]string{\"Namespace\"}, headers...)\n\t}\n\treturn headers\n}\n\nfunc (a kustomizationListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := a.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_receiver.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n)\n\nvar getReceiverCmd = &cobra.Command{\n\tUse:     \"receivers\",\n\tAliases: []string{\"receiver\"},\n\tShort:   \"Get Receiver statuses\",\n\tLong:    `The get receiver command prints the statuses of the resources.`,\n\tExample: `  # List all Receiver and their status\n  flux get receivers`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: receiverType,\n\t\t\tlist:    receiverListAdapter{&notificationv1.ReceiverList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*notificationv1.Receiver)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v receiver\", obj)\n\t\t\t}\n\n\t\t\tsink := receiverListAdapter{&notificationv1.ReceiverList{\n\t\t\t\tItems: []notificationv1.Receiver{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetCmd.AddCommand(getReceiverCmd)\n}\n\nfunc (s receiverListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := s.Items[i]\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\tcases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)\n}\n\nfunc (s receiverListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\treturn append(namespaceHeader, headers...)\n\t}\n\treturn headers\n}\n\nfunc (s receiverListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := s.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_source.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar getSourceCmd = &cobra.Command{\n\tUse:     \"sources\",\n\tAliases: []string{\"source\"},\n\tShort:   \"Get source statuses\",\n\tLong:    `The get source sub-commands print the statuses of the sources.`,\n}\n\nfunc init() {\n\tgetCmd.AddCommand(getSourceCmd)\n}\n"
  },
  {
    "path": "cmd/flux/get_source_all.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tapimeta \"k8s.io/apimachinery/pkg/api/meta\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar getSourceAllCmd = &cobra.Command{\n\tUse:   \"all\",\n\tShort: \"Get all source statuses\",\n\tLong:  withPreviewNote(\"The get sources all command print the statuses of all sources.\"),\n\tExample: `  # List all sources in a namespace\n  flux get sources all --namespace=flux-system\n\n  # List all sources in all namespaces\n  flux get sources all --all-namespaces`,\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\terr := validateWatchOption(cmd, \"all\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar allSourceCmd = []getCommand{\n\t\t\t{\n\t\t\t\tapiType: ociRepositoryType,\n\t\t\t\tlist:    &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tapiType: bucketType,\n\t\t\t\tlist:    &bucketListAdapter{&sourcev1.BucketList{}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tapiType: gitRepositoryType,\n\t\t\t\tlist:    &gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tapiType: helmRepositoryType,\n\t\t\t\tlist:    &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tapiType: helmChartType,\n\t\t\t\tlist:    &helmChartListAdapter{&sourcev1.HelmChartList{}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tapiType: externalArtifactType,\n\t\t\t\tlist:    &externalArtifactListAdapter{&sourcev1.ExternalArtifactList{}},\n\t\t\t},\n\t\t}\n\n\t\tfor _, c := range allSourceCmd {\n\t\t\tif err := c.run(cmd, args); err != nil {\n\t\t\t\tif !apimeta.IsNoMatchError(err) {\n\t\t\t\t\tlogger.Failuref(\"%s\", err.Error())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetSourceCmd.AddCommand(getSourceAllCmd)\n}\n"
  },
  {
    "path": "cmd/flux/get_source_bucket.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar getSourceBucketCmd = &cobra.Command{\n\tUse:   \"bucket\",\n\tShort: \"Get Bucket source statuses\",\n\tLong:  \"The get sources bucket command prints the status of the Bucket sources.\",\n\tExample: `  # List all Buckets and their status\n  flux get sources bucket\n\n # List buckets from all namespaces\n  flux get sources helm --all-namespaces`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: bucketType,\n\t\t\tlist:    &bucketListAdapter{&sourcev1.BucketList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*sourcev1.Bucket)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v bucket\", obj)\n\t\t\t}\n\n\t\t\tsink := &bucketListAdapter{&sourcev1.BucketList{\n\t\t\t\tItems: []sourcev1.Bucket{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetSourceCmd.AddCommand(getSourceBucketCmd)\n}\n\nfunc (a *bucketListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := a.Items[i]\n\tvar revision string\n\tif item.GetArtifact() != nil {\n\t\trevision = item.GetArtifact().Revision\n\t}\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\trevision = utils.TruncateHex(revision)\n\tmsg = utils.TruncateHex(msg)\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\trevision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)\n}\n\nfunc (a bucketListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Revision\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\theaders = append([]string{\"Namespace\"}, headers...)\n\t}\n\treturn headers\n}\n\nfunc (a bucketListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := a.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_source_chart.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar getSourceHelmChartCmd = &cobra.Command{\n\tUse:   \"chart\",\n\tShort: \"Get HelmChart statuses\",\n\tLong:  \"The get sources chart command prints the status of the HelmCharts.\",\n\tExample: `  # List all Helm charts and their status\n  flux get sources chart\n\n # List Helm charts from all namespaces\n  flux get sources chart --all-namespaces`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: helmChartType,\n\t\t\tlist:    &helmChartListAdapter{&sourcev1.HelmChartList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*sourcev1.HelmChart)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v chart\", obj)\n\t\t\t}\n\n\t\t\tsink := &helmChartListAdapter{&sourcev1.HelmChartList{\n\t\t\t\tItems: []sourcev1.HelmChart{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetSourceCmd.AddCommand(getSourceHelmChartCmd)\n}\n\nfunc (a *helmChartListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := a.Items[i]\n\tvar revision string\n\tif item.GetArtifact() != nil {\n\t\trevision = item.GetArtifact().Revision\n\t}\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\t// NB: do not shorten revision as it contains a SemVer\n\t// Message may still contain reference of e.g. commit chart was build from\n\tmsg = utils.TruncateHex(msg)\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\trevision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)\n}\n\nfunc (a helmChartListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Revision\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\theaders = append([]string{\"Namespace\"}, headers...)\n\t}\n\treturn headers\n}\n\nfunc (a helmChartListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := a.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_source_external.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar getSourceExternalCmd = &cobra.Command{\n\tUse:   \"external\",\n\tShort: \"Get ExternalArtifact source statuses\",\n\tLong:  `The get sources external command prints the status of the ExternalArtifact sources.`,\n\tExample: `  # List all ExternalArtifacts and their status\n  flux get sources external\n\n  # List ExternalArtifacts from all namespaces\n  flux get sources external --all-namespaces`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.ExternalArtifactKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: externalArtifactType,\n\t\t\tlist:    &externalArtifactListAdapter{&sourcev1.ExternalArtifactList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*sourcev1.ExternalArtifact)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"impossible to cast type %#v to ExternalArtifact\", obj)\n\t\t\t}\n\n\t\t\tsink := &externalArtifactListAdapter{&sourcev1.ExternalArtifactList{\n\t\t\t\tItems: []sourcev1.ExternalArtifact{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetSourceCmd.AddCommand(getSourceExternalCmd)\n}\n\nfunc (a *externalArtifactListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := a.Items[i]\n\tvar revision string\n\tif item.Status.Artifact != nil {\n\t\trevision = item.Status.Artifact.Revision\n\t}\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\trevision = utils.TruncateHex(revision)\n\tmsg = utils.TruncateHex(msg)\n\n\tvar source string\n\tif item.Spec.SourceRef != nil {\n\t\tsource = fmt.Sprintf(\"%s/%s/%s\",\n\t\t\titem.Spec.SourceRef.Kind,\n\t\t\titem.Spec.SourceRef.Namespace,\n\t\t\titem.Spec.SourceRef.Name)\n\t}\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\trevision, source, status, msg)\n}\n\nfunc (a externalArtifactListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Revision\", \"Source\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\theaders = append([]string{\"Namespace\"}, headers...)\n\t}\n\treturn headers\n}\n\nfunc (a externalArtifactListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := a.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_source_git.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar getSourceGitCmd = &cobra.Command{\n\tUse:   \"git\",\n\tShort: \"Get GitRepository source statuses\",\n\tLong:  `The get sources git command prints the status of the GitRepository sources.`,\n\tExample: `  # List all Git repositories and their status\n  flux get sources git\n\n # List Git repositories from all namespaces\n  flux get sources git --all-namespaces`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: gitRepositoryType,\n\t\t\tlist:    &gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*sourcev1.GitRepository)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v git\", obj)\n\t\t\t}\n\n\t\t\tsink := &gitRepositoryListAdapter{&sourcev1.GitRepositoryList{\n\t\t\t\tItems: []sourcev1.GitRepository{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetSourceCmd.AddCommand(getSourceGitCmd)\n}\n\nfunc (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := a.Items[i]\n\tvar revision string\n\tif item.GetArtifact() != nil {\n\t\trevision = item.GetArtifact().Revision\n\t}\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\trevision = utils.TruncateHex(revision)\n\tmsg = utils.TruncateHex(msg)\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\trevision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)\n}\n\nfunc (a gitRepositoryListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Revision\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\theaders = append([]string{\"Namespace\"}, headers...)\n\t}\n\treturn headers\n}\n\nfunc (a gitRepositoryListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := a.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_source_helm.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar getSourceHelmCmd = &cobra.Command{\n\tUse:   \"helm\",\n\tShort: \"Get HelmRepository source statuses\",\n\tLong:  \"The get sources helm command prints the status of the HelmRepository sources.\",\n\tExample: `  # List all Helm repositories and their status\n  flux get sources helm\n\n # List Helm repositories from all namespaces\n  flux get sources helm --all-namespaces`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: helmRepositoryType,\n\t\t\tlist:    &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*sourcev1.HelmRepository)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"Impossible to cast type %#v helm\", obj)\n\t\t\t}\n\n\t\t\tsink := &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{\n\t\t\t\tItems: []sourcev1.HelmRepository{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetSourceCmd.AddCommand(getSourceHelmCmd)\n}\n\nfunc (a *helmRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := a.Items[i]\n\tvar revision string\n\tif item.GetArtifact() != nil {\n\t\trevision = item.GetArtifact().Revision\n\t}\n\tvar status, msg string\n\tif item.Spec.Type == sourcev1.HelmRepositoryTypeOCI {\n\t\tstatus, msg = string(metav1.ConditionTrue), \"Helm repository is Ready\"\n\t} else {\n\t\tstatus, msg = statusAndMessage(item.Status.Conditions)\n\t}\n\trevision = utils.TruncateHex(revision)\n\tmsg = utils.TruncateHex(msg)\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\trevision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)\n}\n\nfunc (a helmRepositoryListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Revision\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\theaders = append([]string{\"Namespace\"}, headers...)\n\t}\n\treturn headers\n}\n\nfunc (a helmRepositoryListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := a.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_source_oci.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar getSourceOCIRepositoryCmd = &cobra.Command{\n\tUse:   \"oci\",\n\tShort: \"Get OCIRepository status\",\n\tLong:  withPreviewNote(\"The get sources oci command prints the status of the OCIRepository sources.\"),\n\tExample: `  # List all OCIRepositories and their status\n  flux get sources oci\n\n  # List OCIRepositories from all namespaces\n  flux get sources oci --all-namespaces`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),\n\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\tget := getCommand{\n\t\t\tapiType: ociRepositoryType,\n\t\t\tlist:    &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},\n\t\t\tfuncMap: make(typeMap),\n\t\t}\n\n\t\terr := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {\n\t\t\to, ok := obj.(*sourcev1.OCIRepository)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"impossible to cast type %#v to OCIRepository\", obj)\n\t\t\t}\n\n\t\t\tsink := &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{\n\t\t\t\tItems: []sourcev1.OCIRepository{\n\t\t\t\t\t*o,\n\t\t\t\t}}}\n\t\t\treturn sink, nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := get.run(cmd, args); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nfunc init() {\n\tgetSourceCmd.AddCommand(getSourceOCIRepositoryCmd)\n}\n\nfunc (a *ociRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {\n\titem := a.Items[i]\n\tvar revision string\n\tif item.GetArtifact() != nil {\n\t\trevision = item.GetArtifact().Revision\n\t}\n\tstatus, msg := statusAndMessage(item.Status.Conditions)\n\trevision = utils.TruncateHex(revision)\n\tmsg = utils.TruncateHex(msg)\n\treturn append(nameColumns(&item, includeNamespace, includeKind),\n\t\trevision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)\n}\n\nfunc (a ociRepositoryListAdapter) headers(includeNamespace bool) []string {\n\theaders := []string{\"Name\", \"Revision\", \"Suspended\", \"Ready\", \"Message\"}\n\tif includeNamespace {\n\t\theaders = append([]string{\"Namespace\"}, headers...)\n\t}\n\treturn headers\n}\n\nfunc (a ociRepositoryListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {\n\titem := a.Items[i]\n\treturn statusMatches(conditionType, conditionStatus, item.Status.Conditions)\n}\n"
  },
  {
    "path": "cmd/flux/get_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc Test_GetCmd(t *testing.T) {\n\ttmpl := map[string]string{\n\t\t\"fluxns\": allocateNamespace(\"flux-system\"),\n\t}\n\ttestEnv.CreateObjectFile(\"./testdata/get/objects.yaml\", tmpl, t)\n\n\ttests := []struct {\n\t\tname     string\n\t\targs     string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"no label selector\",\n\t\t\texpected: \"testdata/get/get.golden\",\n\t\t},\n\t\t{\n\t\t\tname:     \"equal label selector\",\n\t\t\targs:     \"-l sharding.fluxcd.io/key=shard1\",\n\t\t\texpected: \"testdata/get/get_label_one.golden\",\n\t\t},\n\t\t{\n\t\t\tname:     \"notin label selector\",\n\t\t\targs:     `-l \"sharding.fluxcd.io/key notin (shard1, shard2)\"`,\n\t\t\texpected: \"testdata/get/get_label_two.golden\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   \"get sources git \" + tt.args + \" -n \" + tmpl[\"fluxns\"],\n\t\t\t\tassert: assertGoldenTemplateFile(tt.expected, nil),\n\t\t\t}\n\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n\nfunc Test_GetCmdErrors(t *testing.T) {\n\ttmpl := map[string]string{\n\t\t\"fluxns\": allocateNamespace(\"flux-system\"),\n\t}\n\ttestEnv.CreateObjectFile(\"./testdata/get/objects.yaml\", tmpl, t)\n\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\tname:   \"specific object not found\",\n\t\t\targs:   \"get kustomization non-existent-resource -n \" + tmpl[\"fluxns\"],\n\t\t\tassert: assertError(fmt.Sprintf(\"Kustomization object 'non-existent-resource' not found in \\\"%s\\\" namespace\", tmpl[\"fluxns\"])),\n\t\t},\n\t\t{\n\t\t\tname:   \"no objects found in namespace\",\n\t\t\targs:   \"get helmrelease -n \" + tmpl[\"fluxns\"],\n\t\t\tassert: assertError(fmt.Sprintf(\"no HelmRelease objects found in \\\"%s\\\" namespace\", tmpl[\"fluxns\"])),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n\nfunc Test_GetCmdSuccess(t *testing.T) {\n\ttmpl := map[string]string{\n\t\t\"fluxns\": allocateNamespace(\"flux-system\"),\n\t}\n\ttestEnv.CreateObjectFile(\"./testdata/get/objects.yaml\", tmpl, t)\n\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\tname:   \"list sources git\",\n\t\t\targs:   \"get sources git -n \" + tmpl[\"fluxns\"],\n\t\t\tassert: assertSuccess(),\n\t\t},\n\t\t{\n\t\t\tname:   \"get help\",\n\t\t\targs:   \"get --help\",\n\t\t\tassert: assertSuccess(),\n\t\t},\n\t\t{\n\t\t\tname:   \"get with all namespaces flag\",\n\t\t\targs:   \"get sources git -A\",\n\t\t\tassert: assertSuccess(),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/helmrelease.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n)\n\n// helmv2.HelmRelease\n\nvar helmReleaseType = apiType{\n\tkind:         helmv2.HelmReleaseKind,\n\thumanKind:    \"helmrelease\",\n\tgroupVersion: helmv2.GroupVersion,\n}\n\ntype helmReleaseAdapter struct {\n\t*helmv2.HelmRelease\n}\n\nfunc (h helmReleaseAdapter) asClientObject() client.Object {\n\treturn h.HelmRelease\n}\n\nfunc (h helmReleaseAdapter) deepCopyClientObject() client.Object {\n\treturn h.HelmRelease.DeepCopy()\n}\n\n// helmv2.HelmReleaseList\n\ntype helmReleaseListAdapter struct {\n\t*helmv2.HelmReleaseList\n}\n\nfunc (h helmReleaseListAdapter) asClientList() client.ObjectList {\n\treturn h.HelmReleaseList\n}\n\nfunc (h helmReleaseListAdapter) len() int {\n\treturn len(h.HelmReleaseList.Items)\n}\n"
  },
  {
    "path": "cmd/flux/helmrelease_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport \"testing\"\n\nfunc TestHelmReleaseFromGit(t *testing.T) {\n\tnamespace := allocateNamespace(\"thrfg\")\n\tdel, err := execSetupTestNamespace(namespace)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Cleanup(del)\n\n\ttmpl := map[string]string{\"ns\": namespace}\n\n\tcases := []struct {\n\t\targs       string\n\t\tgoldenFile string\n\t\ttmpl       map[string]string\n\t}{\n\t\t{\n\t\t\t\"create source git thrfg --url=https://github.com/stefanprodan/podinfo --branch=main --tag=6.3.5\",\n\t\t\t\"testdata/helmrelease/create_source_git.golden\",\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"create helmrelease thrfg --source=GitRepository/thrfg --chart=./charts/podinfo\",\n\t\t\t\"testdata/helmrelease/create_helmrelease_from_git.golden\",\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"get helmrelease thrfg\",\n\t\t\t\"testdata/helmrelease/get_helmrelease_from_git.golden\",\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"reconcile helmrelease thrfg --with-source\",\n\t\t\t\"testdata/helmrelease/reconcile_helmrelease_from_git.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"suspend helmrelease thrfg\",\n\t\t\t\"testdata/helmrelease/suspend_helmrelease_from_git.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"resume helmrelease thrfg\",\n\t\t\t\"testdata/helmrelease/resume_helmrelease_from_git.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"delete helmrelease thrfg --silent\",\n\t\t\t\"testdata/helmrelease/delete_helmrelease_from_git.golden\",\n\t\t\ttmpl,\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tcmd := cmdTestCase{\n\t\t\targs:   tc.args + \" -n=\" + namespace,\n\t\t\tassert: assertGoldenTemplateFile(tc.goldenFile, tc.tmpl),\n\t\t}\n\t\tcmd.runTestCmd(t)\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/image.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\n// These are general-purpose adapters for attaching methods to, for\n// the various commands. The *List adapters implement len(), since\n// it's used in at least a couple of commands.\n\n// imagev1.ImageRepository\n\nvar imageRepositoryType = apiType{\n\tkind:         imagev1.ImageRepositoryKind,\n\thumanKind:    \"image repository\",\n\tgroupVersion: imagev1.GroupVersion,\n}\n\ntype imageRepositoryAdapter struct {\n\t*imagev1.ImageRepository\n}\n\nfunc (a imageRepositoryAdapter) asClientObject() client.Object {\n\treturn a.ImageRepository\n}\n\nfunc (a imageRepositoryAdapter) deepCopyClientObject() client.Object {\n\treturn a.ImageRepository.DeepCopy()\n}\n\n// imagev1.ImageRepositoryList\n\ntype imageRepositoryListAdapter struct {\n\t*imagev1.ImageRepositoryList\n}\n\nfunc (a imageRepositoryListAdapter) asClientList() client.ObjectList {\n\treturn a.ImageRepositoryList\n}\n\nfunc (a imageRepositoryListAdapter) len() int {\n\treturn len(a.ImageRepositoryList.Items)\n}\n\n// imagev1.ImagePolicy\n\nvar imagePolicyType = apiType{\n\tkind:         imagev1.ImagePolicyKind,\n\thumanKind:    \"image policy\",\n\tgroupVersion: imagev1.GroupVersion,\n}\n\ntype imagePolicyAdapter struct {\n\t*imagev1.ImagePolicy\n}\n\nfunc (a imagePolicyAdapter) asClientObject() client.Object {\n\treturn a.ImagePolicy\n}\n\nfunc (a imagePolicyAdapter) deepCopyClientObject() client.Object {\n\treturn a.ImagePolicy.DeepCopy()\n}\n\nfunc (a imagePolicyAdapter) isStatic() bool {\n\treturn false\n}\n\nfunc (a imagePolicyAdapter) lastHandledReconcileRequest() string {\n\treturn a.Status.GetLastHandledReconcileRequest()\n}\n\nfunc (a imagePolicyAdapter) isSuspended() bool {\n\treturn a.Spec.Suspend\n}\n\nfunc (a imagePolicyAdapter) setSuspended() {\n\ta.Spec.Suspend = true\n}\n\nfunc (a imagePolicyAdapter) successMessage() string {\n\treturn fmt.Sprintf(\"selected ref %s\", a.Status.LatestRef.String())\n}\n\nfunc (a imagePolicyAdapter) setUnsuspended() {\n\ta.Spec.Suspend = false\n}\n\n// imagev1.ImagePolicyList\n\ntype imagePolicyListAdapter struct {\n\t*imagev1.ImagePolicyList\n}\n\nfunc (a imagePolicyListAdapter) asClientList() client.ObjectList {\n\treturn a.ImagePolicyList\n}\n\nfunc (a imagePolicyListAdapter) len() int {\n\treturn len(a.ImagePolicyList.Items)\n}\n\nfunc (a imagePolicyListAdapter) resumeItem(i int) resumable {\n\treturn &imagePolicyAdapter{&a.ImagePolicyList.Items[i]}\n}\n\nfunc (obj imagePolicyAdapter) getObservedGeneration() int64 {\n\treturn obj.ImagePolicy.Status.ObservedGeneration\n}\n\nfunc (a imagePolicyListAdapter) item(i int) suspendable {\n\treturn &imagePolicyAdapter{&a.ImagePolicyList.Items[i]}\n}\n\n// autov1.ImageUpdateAutomation\n\nvar imageUpdateAutomationType = apiType{\n\tkind:         autov1.ImageUpdateAutomationKind,\n\thumanKind:    \"image update automation\",\n\tgroupVersion: autov1.GroupVersion,\n}\n\ntype imageUpdateAutomationAdapter struct {\n\t*autov1.ImageUpdateAutomation\n}\n\nfunc (a imageUpdateAutomationAdapter) asClientObject() client.Object {\n\treturn a.ImageUpdateAutomation\n}\n\nfunc (a imageUpdateAutomationAdapter) deepCopyClientObject() client.Object {\n\treturn a.ImageUpdateAutomation.DeepCopy()\n}\n\n// autov1.ImageUpdateAutomationList\n\ntype imageUpdateAutomationListAdapter struct {\n\t*autov1.ImageUpdateAutomationList\n}\n\nfunc (a imageUpdateAutomationListAdapter) asClientList() client.ObjectList {\n\treturn a.ImageUpdateAutomationList\n}\n\nfunc (a imageUpdateAutomationListAdapter) len() int {\n\treturn len(a.ImageUpdateAutomationList.Items)\n}\n"
  },
  {
    "path": "cmd/flux/image_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport \"testing\"\n\nfunc TestImageScanning(t *testing.T) {\n\tnamespace := allocateNamespace(\"tis\")\n\tdel, err := execSetupTestNamespace(namespace)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Cleanup(del)\n\n\tcases := []struct {\n\t\targs       string\n\t\tgoldenFile string\n\t}{\n\t\t{\n\t\t\t\"create image repository podinfo --image=ghcr.io/stefanprodan/podinfo --interval=10m\",\n\t\t\t\"testdata/image/create_image_repository.golden\",\n\t\t},\n\t\t{\n\t\t\t\"create image policy podinfo-semver --image-ref=podinfo --interval=10m --reflect-digest=Always --select-semver=5.0.x\",\n\t\t\t\"testdata/image/create_image_policy.golden\",\n\t\t},\n\t\t{\n\t\t\t\"get image policy podinfo-semver\",\n\t\t\t\"testdata/image/get_image_policy_semver.golden\",\n\t\t},\n\t\t{\n\t\t\t`create image policy podinfo-regex --image-ref=podinfo --select-semver=\">4.0.0\" --filter-regex=\"5\\.0\\.0\"`,\n\t\t\t\"testdata/image/create_image_policy.golden\",\n\t\t},\n\t\t{\n\t\t\t\"get image policy podinfo-regex\",\n\t\t\t\"testdata/image/get_image_policy_regex.golden\",\n\t\t},\n\t\t{\n\t\t\t\"suspend image policy podinfo-semver\",\n\t\t\t\"testdata/image/suspend_image_policy.golden\",\n\t\t},\n\t\t{\n\t\t\t\"resume image policy podinfo-semver\",\n\t\t\t\"testdata/image/resume_image_policy.golden\",\n\t\t},\n\t\t{\n\t\t\t\"reconcile image policy podinfo-semver\",\n\t\t\t\"testdata/image/reconcile_image_policy.golden\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tcmd := cmdTestCase{\n\t\t\targs:   tc.args + \" -n=\" + namespace,\n\t\t\tassert: assertGoldenFile(tc.goldenFile),\n\t\t}\n\t\tcmd.runTestCmd(t)\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/install.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/manifoldco/promptui\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/fluxcd/flux2/v2/pkg/status\"\n)\n\nvar installCmd = &cobra.Command{\n\tUse:   \"install\",\n\tArgs:  cobra.NoArgs,\n\tShort: \"Install or upgrade Flux\",\n\tLong: `The install command deploys Flux in the specified namespace.\nIf a previous version is installed, then an in-place upgrade will be performed.`,\n\tExample: `  # Install the latest version in the flux-system namespace\n  flux install --namespace=flux-system\n\n  # Install a specific series of components\n  flux install --components=\"source-controller,kustomize-controller\"\n\n  # Install all components including the image automation ones\n  flux install --components-extra=\"image-reflector-controller,image-automation-controller\"\n\n  # Install Flux onto tainted Kubernetes nodes\n  flux install --toleration-keys=node.kubernetes.io/dedicated-to-flux\n\n  # Dry-run install\n  flux install --export | kubectl apply --dry-run=client -f- \n\n  # Write install manifests to file\n  flux install --export > flux-system.yaml`,\n\tRunE: installCmdRun,\n}\n\ntype installFlags struct {\n\texport             bool\n\tversion            string\n\tdefaultComponents  []string\n\textraComponents    []string\n\tregistry           string\n\tregistryCredential string\n\timagePullSecret    string\n\tbranch             string\n\twatchAllNamespaces bool\n\tnetworkPolicy      bool\n\tmanifestsPath      string\n\tlogLevel           flags.LogLevel\n\ttokenAuth          bool\n\tclusterDomain      string\n\ttolerationKeys     []string\n\tforce              bool\n}\n\nvar installArgs = newInstallFlags()\n\nfunc init() {\n\tinstallCmd.Flags().BoolVar(&installArgs.export, \"export\", false,\n\t\t\"write the install manifests to stdout and exit\")\n\tinstallCmd.Flags().StringVarP(&installArgs.version, \"version\", \"v\", \"\",\n\t\t\"toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases\")\n\tinstallCmd.Flags().StringSliceVar(&installArgs.defaultComponents, \"components\", rootArgs.defaults.Components,\n\t\t\"list of components, accepts comma-separated values\")\n\tinstallCmd.Flags().StringSliceVar(&installArgs.extraComponents, \"components-extra\", nil,\n\t\t\"list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller,source-watcher'\")\n\tinstallCmd.Flags().StringVar(&installArgs.manifestsPath, \"manifests\", \"\", \"path to the manifest directory\")\n\tinstallCmd.Flags().StringVar(&installArgs.registry, \"registry\", rootArgs.defaults.Registry,\n\t\t\"container registry where the toolkit images are published\")\n\tinstallCmd.Flags().StringVar(&installArgs.registryCredential, \"registry-creds\", \"\",\n\t\t\"container registry credentials in the format 'user:password', requires --image-pull-secret to be set\")\n\tinstallCmd.Flags().StringVar(&installArgs.imagePullSecret, \"image-pull-secret\", \"\",\n\t\t\"Kubernetes secret name used for pulling the toolkit images from a private registry\")\n\tinstallCmd.Flags().BoolVar(&installArgs.watchAllNamespaces, \"watch-all-namespaces\", rootArgs.defaults.WatchAllNamespaces,\n\t\t\"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed\")\n\tinstallCmd.Flags().Var(&installArgs.logLevel, \"log-level\", installArgs.logLevel.Description())\n\tinstallCmd.Flags().BoolVar(&installArgs.networkPolicy, \"network-policy\", rootArgs.defaults.NetworkPolicy,\n\t\t\"deny ingress access to the toolkit controllers from other namespaces using network policies\")\n\tinstallCmd.Flags().StringVar(&installArgs.clusterDomain, \"cluster-domain\", rootArgs.defaults.ClusterDomain, \"internal cluster domain\")\n\tinstallCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, \"toleration-keys\", nil,\n\t\t\"list of toleration keys used to schedule the components pods onto nodes with matching taints\")\n\tinstallCmd.Flags().BoolVar(&installArgs.force, \"force\", false, \"override existing Flux installation if it's managed by a different tool such as Helm\")\n\tinstallCmd.Flags().MarkHidden(\"manifests\")\n\n\trootCmd.AddCommand(installCmd)\n}\n\nfunc newInstallFlags() installFlags {\n\treturn installFlags{\n\t\tlogLevel:           flags.LogLevel(rootArgs.defaults.LogLevel),\n\t\tdefaultComponents:  rootArgs.defaults.Components,\n\t\tregistry:           rootArgs.defaults.Registry,\n\t\twatchAllNamespaces: rootArgs.defaults.WatchAllNamespaces,\n\t\tnetworkPolicy:      rootArgs.defaults.NetworkPolicy,\n\t\tclusterDomain:      rootArgs.defaults.ClusterDomain,\n\t}\n}\n\nfunc installCmdRun(cmd *cobra.Command, args []string) error {\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tcomponents := append(installArgs.defaultComponents, installArgs.extraComponents...)\n\terr := utils.ValidateComponents(components)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif installArgs.registryCredential != \"\" && installArgs.imagePullSecret == \"\" {\n\t\treturn fmt.Errorf(\"--registry-creds requires --image-pull-secret to be set\")\n\t}\n\n\tif installArgs.registryCredential != \"\" && len(strings.Split(installArgs.registryCredential, \":\")) != 2 {\n\t\treturn fmt.Errorf(\"invalid --registry-creds format, expected 'user:password'\")\n\t}\n\n\tif ver, err := getVersion(installArgs.version); err != nil {\n\t\treturn err\n\t} else {\n\t\tinstallArgs.version = ver\n\t}\n\n\tif !installArgs.export {\n\t\tlogger.Generatef(\"generating manifests\")\n\t}\n\n\ttmpDir, err := manifestgen.MkdirTempAbs(\"\", *kubeconfigArgs.Namespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(tmpDir)\n\n\tmanifestsBase := \"\"\n\tif isEmbeddedVersion(installArgs.version) {\n\t\tif err := writeEmbeddedManifests(tmpDir); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tmanifestsBase = tmpDir\n\t}\n\n\topts := install.Options{\n\t\tBaseURL:                installArgs.manifestsPath,\n\t\tVersion:                installArgs.version,\n\t\tNamespace:              *kubeconfigArgs.Namespace,\n\t\tComponents:             components,\n\t\tRegistry:               installArgs.registry,\n\t\tRegistryCredential:     installArgs.registryCredential,\n\t\tImagePullSecret:        installArgs.imagePullSecret,\n\t\tWatchAllNamespaces:     installArgs.watchAllNamespaces,\n\t\tNetworkPolicy:          installArgs.networkPolicy,\n\t\tLogLevel:               installArgs.logLevel.String(),\n\t\tNotificationController: rootArgs.defaults.NotificationController,\n\t\tManifestFile:           fmt.Sprintf(\"%s.yaml\", *kubeconfigArgs.Namespace),\n\t\tTimeout:                rootArgs.timeout,\n\t\tClusterDomain:          installArgs.clusterDomain,\n\t\tTolerationKeys:         installArgs.tolerationKeys,\n\t}\n\n\tif installArgs.manifestsPath == \"\" {\n\t\topts.BaseURL = install.MakeDefaultOptions().BaseURL\n\t}\n\n\tmanifest, err := install.Generate(opts, manifestsBase)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"install failed: %w\", err)\n\t}\n\n\tif _, err := manifest.WriteFile(tmpDir); err != nil {\n\t\treturn fmt.Errorf(\"install failed: %w\", err)\n\t}\n\n\tif installArgs.export {\n\t\t_, err = rootCmd.OutOrStdout().Write([]byte(manifest.Content))\n\t\treturn err\n\t} else if rootArgs.verbose {\n\t\t_, err = rootCmd.OutOrStdout().Write([]byte(manifest.Content))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tlogger.Successf(\"manifests build completed\")\n\tlogger.Actionf(\"installing components in %s namespace\", *kubeconfigArgs.Namespace)\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinstalled := true\n\tinfo, err := getFluxClusterInfo(ctx, kubeClient)\n\tif err != nil {\n\t\tif !errors.IsNotFound(err) {\n\t\t\treturn fmt.Errorf(\"cluster info unavailable: %w\", err)\n\t\t}\n\t\tinstalled = false\n\t}\n\n\tif info.bootstrapped {\n\t\treturn fmt.Errorf(\"this cluster has already been bootstrapped with Flux %s! Please use 'flux bootstrap' to upgrade\",\n\t\t\tinfo.version)\n\t}\n\n\tif installed && !installArgs.force {\n\t\terr := confirmFluxInstallOverride(info)\n\t\tif err != nil {\n\t\t\tif err == promptui.ErrAbort {\n\t\t\t\treturn fmt.Errorf(\"installation cancelled\")\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t}\n\n\tapplyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"install failed: %w\", err)\n\t}\n\n\trootCmd.Println(applyOutput)\n\n\tif opts.ImagePullSecret != \"\" && opts.RegistryCredential != \"\" {\n\t\tlogger.Actionf(\"generating image pull secret %s\", opts.ImagePullSecret)\n\t\tcredentials := strings.SplitN(opts.RegistryCredential, \":\", 2)\n\t\tsecretOpts := sourcesecret.Options{\n\t\t\tName:      opts.ImagePullSecret,\n\t\t\tNamespace: opts.Namespace,\n\t\t\tRegistry:  opts.Registry,\n\t\t\tUsername:  credentials[0],\n\t\t\tPassword:  credentials[1],\n\t\t}\n\t\timagePullSecret, err := sourcesecret.GenerateOCI(secretOpts)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"install failed: %w\", err)\n\t\t}\n\t\tvar s corev1.Secret\n\t\tif err := yaml.Unmarshal([]byte(imagePullSecret.Content), &s); err != nil {\n\t\t\treturn fmt.Errorf(\"install failed: %w\", err)\n\t\t}\n\t\tif err := upsertSecret(ctx, kubeClient, s); err != nil {\n\t\t\treturn fmt.Errorf(\"install failed: %w\", err)\n\t\t}\n\t}\n\n\tkubeConfig, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"install failed: %w\", err)\n\t}\n\tstatusChecker, err := status.NewStatusChecker(kubeConfig, 5*time.Second, rootArgs.timeout, logger)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"install failed: %w\", err)\n\t}\n\tcomponentRefs, err := buildComponentObjectRefs(components...)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"install failed: %w\", err)\n\t}\n\tlogger.Waitingf(\"verifying installation\")\n\tif err := statusChecker.Assess(componentRefs...); err != nil {\n\t\treturn fmt.Errorf(\"install failed\")\n\t}\n\n\tlogger.Successf(\"install finished\")\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/install_test.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\n\tssautil \"github.com/fluxcd/pkg/ssa/utils\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n)\n\nfunc TestInstall(t *testing.T) {\n\t// The pointer to kubeconfigArgs.Namespace is shared across\n\t// the tests. When a new value is set, it will linger and\n\t// impact subsequent tests.\n\t// Given that this test uses an invalid namespace, it ensures\n\t// to restore whatever value it had previously.\n\tcurrentNamespace := *kubeconfigArgs.Namespace\n\tt.Cleanup(func() { *kubeconfigArgs.Namespace = currentNamespace })\n\n\ttests := []struct {\n\t\tname   string\n\t\targs   string\n\t\tassert assertFunc\n\t}{\n\t\t{\n\t\t\tname:   \"invalid namespace\",\n\t\t\targs:   \"install --namespace='@#[]'\",\n\t\t\tassert: assertError(\"namespace must be a valid DNS label: \\\"@#[]\\\"\"),\n\t\t},\n\t\t{\n\t\t\tname:   \"invalid sub-command\",\n\t\t\targs:   \"install unexpectedPosArg --namespace=example\",\n\t\t\tassert: assertError(`unknown command \"unexpectedPosArg\" for \"flux install\"`),\n\t\t},\n\t\t{\n\t\t\tname:   \"missing image pull secret\",\n\t\t\targs:   \"install --registry-creds=fluxcd:test\",\n\t\t\tassert: assertError(`--registry-creds requires --image-pull-secret to be set`),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tt.args,\n\t\t\t\tassert: tt.assert,\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n\nfunc TestInstall_ComponentsExtra(t *testing.T) {\n\tg := NewWithT(t)\n\tcommand := \"install --export --components-extra=\" +\n\t\tstrings.Join(install.MakeDefaultOptions().ComponentsExtra, \",\")\n\n\toutput, err := executeCommand(command)\n\tg.Expect(err).NotTo(HaveOccurred())\n\n\tmanifests, err := ssautil.ReadObjects(strings.NewReader(output))\n\tg.Expect(err).NotTo(HaveOccurred())\n\n\tfoundImageAutomation := false\n\tfoundImageReflector := false\n\tfoundSourceWatcher := false\n\tfoundExternalArtifact := false\n\tfor _, obj := range manifests {\n\t\tif obj.GetKind() == \"Deployment\" && obj.GetName() == \"image-automation-controller\" {\n\t\t\tfoundImageAutomation = true\n\t\t}\n\t\tif obj.GetKind() == \"Deployment\" && obj.GetName() == \"image-reflector-controller\" {\n\t\t\tfoundImageReflector = true\n\t\t}\n\t\tif obj.GetKind() == \"Deployment\" && obj.GetName() == \"source-watcher\" {\n\t\t\tfoundSourceWatcher = true\n\t\t}\n\t\tif obj.GetKind() == \"Deployment\" &&\n\t\t\t(obj.GetName() == \"kustomize-controller\" || obj.GetName() == \"helm-controller\") {\n\t\t\tcontainers, _, _ := unstructured.NestedSlice(obj.Object, \"spec\", \"template\", \"spec\", \"containers\")\n\t\t\tg.Expect(containers).ToNot(BeEmpty())\n\t\t\targs, _, _ := unstructured.NestedSlice(containers[0].(map[string]any), \"args\")\n\t\t\tg.Expect(args).To(ContainElement(\"--feature-gates=ExternalArtifact=true\"))\n\t\t\tfoundExternalArtifact = true\n\t\t}\n\t}\n\tg.Expect(foundImageAutomation).To(BeTrue(), \"image-automation-controller deployment not found\")\n\tg.Expect(foundImageReflector).To(BeTrue(), \"image-reflector-controller deployment not found\")\n\tg.Expect(foundSourceWatcher).To(BeTrue(), \"source-watcher deployment not found\")\n\tg.Expect(foundExternalArtifact).To(BeTrue(), \"ExternalArtifact feature gate not found\")\n}\n"
  },
  {
    "path": "cmd/flux/kustomization.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n)\n\n// kustomizev1.Kustomization\n\nvar kustomizationType = apiType{\n\tkind:         kustomizev1.KustomizationKind,\n\thumanKind:    \"kustomization\",\n\tgroupVersion: kustomizev1.GroupVersion,\n}\n\ntype kustomizationAdapter struct {\n\t*kustomizev1.Kustomization\n}\n\nfunc (a kustomizationAdapter) asClientObject() client.Object {\n\treturn a.Kustomization\n}\n\nfunc (a kustomizationAdapter) deepCopyClientObject() client.Object {\n\treturn a.Kustomization.DeepCopy()\n}\n\n// kustomizev1.KustomizationList\n\ntype kustomizationListAdapter struct {\n\t*kustomizev1.KustomizationList\n}\n\nfunc (a kustomizationListAdapter) asClientList() client.ObjectList {\n\treturn a.KustomizationList\n}\n\nfunc (a kustomizationListAdapter) len() int {\n\treturn len(a.KustomizationList.Items)\n}\n"
  },
  {
    "path": "cmd/flux/kustomization_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport \"testing\"\n\nfunc TestKustomizationFromGit(t *testing.T) {\n\tnamespace := allocateNamespace(\"tkfg\")\n\tdel, err := execSetupTestNamespace(namespace)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Cleanup(del)\n\n\ttmpl := map[string]string{\"ns\": namespace}\n\n\tcases := []struct {\n\t\targs       string\n\t\tgoldenFile string\n\t\ttmpl       map[string]string\n\t}{\n\t\t{\n\t\t\t\"create source git tkfg --url=https://github.com/stefanprodan/podinfo --branch=main --tag=6.3.5\",\n\t\t\t\"testdata/kustomization/create_source_git.golden\",\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"create kustomization tkfg --source=tkfg --path=./deploy/overlays/dev --prune=true --interval=5m --health-check=Deployment/frontend.dev --health-check=Deployment/backend.dev --health-check-timeout=3m\",\n\t\t\t\"testdata/kustomization/create_kustomization_from_git.golden\",\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"get kustomization tkfg\",\n\t\t\t\"testdata/kustomization/get_kustomization_from_git.golden\",\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"reconcile kustomization tkfg --with-source\",\n\t\t\t\"testdata/kustomization/reconcile_kustomization_from_git.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"suspend kustomization tkfg\",\n\t\t\t\"testdata/kustomization/suspend_kustomization_from_git.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"suspend kustomization tkfg foo tkfg bar\",\n\t\t\t\"testdata/kustomization/suspend_kustomization_from_git_multiple_args.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"resume kustomization tkfg foo --wait\",\n\t\t\t\"testdata/kustomization/resume_kustomization_from_git_multiple_args_wait.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"resume kustomization tkfg\",\n\t\t\t\"testdata/kustomization/resume_kustomization_from_git.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"resume kustomization tkfg tkfg\",\n\t\t\t\"testdata/kustomization/resume_kustomization_from_git_multiple_args.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"delete kustomization tkfg --silent\",\n\t\t\t\"testdata/kustomization/delete_kustomization_from_git.golden\",\n\t\t\ttmpl,\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tcmd := cmdTestCase{\n\t\t\targs:   tc.args + \" -n=\" + namespace,\n\t\t\tassert: assertGoldenTemplateFile(tc.goldenFile, tc.tmpl),\n\t\t}\n\t\tcmd.runTestCmd(t)\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/list.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar listCmd = &cobra.Command{\n\tUse:   \"list\",\n\tShort: \"List artifacts\",\n\tLong:  `The list command is used for printing the OCI artifacts metadata.`,\n}\n\nfunc init() {\n\trootCmd.AddCommand(listCmd)\n}\n"
  },
  {
    "path": "cmd/flux/list_artifact.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/google/go-containerregistry/pkg/crane\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/fluxcd/pkg/oci\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/pkg/printers\"\n)\n\ntype listArtifactFlags struct {\n\tsemverFilter string\n\tregexFilter  string\n\tcreds        string\n\tprovider     flags.SourceOCIProvider\n\tinsecure     bool\n}\n\nvar listArtifactArgs = newListArtifactFlags()\n\nfunc newListArtifactFlags() listArtifactFlags {\n\treturn listArtifactFlags{\n\t\tprovider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),\n\t}\n}\n\nvar listArtifactsCmd = &cobra.Command{\n\tUse:   \"artifacts\",\n\tShort: \"list artifacts\",\n\tLong: `The list command fetches the tags and their metadata from a remote OCI repository.\nThe command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`,\n\tExample: `  # List the artifacts stored in an OCI repository\n  flux list artifacts oci://ghcr.io/org/config/app\n`,\n\tRunE: listArtifactsCmdRun,\n}\n\nfunc init() {\n\tlistArtifactsCmd.Flags().StringVar(&listArtifactArgs.semverFilter, \"filter-semver\", \"\", \"filter tags returned from the oci repository using semver\")\n\tlistArtifactsCmd.Flags().StringVar(&listArtifactArgs.regexFilter, \"filter-regex\", \"\", \"filter tags returned from the oci repository using regex\")\n\tlistArtifactsCmd.Flags().StringVar(&listArtifactArgs.creds, \"creds\", \"\", \"credentials for OCI registry in the format <username>[:<password>] if --provider is generic\")\n\tlistArtifactsCmd.Flags().Var(&listArtifactArgs.provider, \"provider\", listArtifactArgs.provider.Description())\n\tlistArtifactsCmd.Flags().BoolVar(&listArtifactArgs.insecure, \"insecure-registry\", false, \"allows the remote artifacts list to be fetched without TLS\")\n\n\tlistCmd.AddCommand(listArtifactsCmd)\n}\n\nfunc listArtifactsCmdRun(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"artifact repository URL is required\")\n\t}\n\tociURL := args[0]\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\turl, err := oci.ParseArtifactURL(ociURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tociOpts := oci.DefaultOptions()\n\n\tif listArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {\n\t\tlogger.Actionf(\"logging in to registry with provider credentials\")\n\t\tociOpt, _, err := loginWithProvider(ctx, url, listArtifactArgs.provider.String())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error during login with provider: %w\", err)\n\t\t}\n\t\tociOpts = append(ociOpts, ociOpt)\n\t}\n\n\tif listArtifactArgs.insecure {\n\t\tociOpts = append(ociOpts, crane.Insecure)\n\t}\n\n\tociClient := oci.NewClient(ociOpts)\n\n\tif listArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && listArtifactArgs.creds != \"\" {\n\t\tlogger.Actionf(\"logging in to registry with credentials\")\n\t\tif err := ociClient.LoginWithCredentials(listArtifactArgs.creds); err != nil {\n\t\t\treturn fmt.Errorf(\"could not login with credentials: %w\", err)\n\t\t}\n\t}\n\n\topts := oci.ListOptions{\n\t\tRegexFilter:  listArtifactArgs.regexFilter,\n\t\tSemverFilter: listArtifactArgs.semverFilter,\n\t}\n\n\tmetas, err := ociClient.List(ctx, url, opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar rows [][]string\n\tfor _, meta := range metas {\n\t\trows = append(rows, []string{meta.URL, meta.Digest, meta.Source, meta.Revision})\n\t}\n\n\terr = printers.TablePrinter([]string{\"artifact\", \"digest\", \"source\", \"revision\"}).Print(cmd.OutOrStdout(), rows)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/log.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\ntype stderrLogger struct {\n\tstderr io.Writer\n}\n\nfunc (l stderrLogger) Actionf(format string, a ...interface{}) {\n\tfmt.Fprintln(l.stderr, `►`, fmt.Sprintf(format, a...))\n}\n\nfunc (l stderrLogger) Generatef(format string, a ...interface{}) {\n\tfmt.Fprintln(l.stderr, `✚`, fmt.Sprintf(format, a...))\n}\n\nfunc (l stderrLogger) Waitingf(format string, a ...interface{}) {\n\tfmt.Fprintln(l.stderr, `◎`, fmt.Sprintf(format, a...))\n}\n\nfunc (l stderrLogger) Successf(format string, a ...interface{}) {\n\tfmt.Fprintln(l.stderr, `✔`, fmt.Sprintf(format, a...))\n}\n\nfunc (l stderrLogger) Warningf(format string, a ...interface{}) {\n\tfmt.Fprintln(l.stderr, `⚠️`, fmt.Sprintf(format, a...))\n}\n\nfunc (l stderrLogger) Failuref(format string, a ...interface{}) {\n\tfmt.Fprintln(l.stderr, `✗`, fmt.Sprintf(format, a...))\n}\n"
  },
  {
    "path": "cmd/flux/logs.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/kubectl/pkg/util\"\n\t\"k8s.io/kubectl/pkg/util/podutils\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n)\n\nvar logsCmd = &cobra.Command{\n\tUse:   \"logs\",\n\tShort: \"Display formatted logs for Flux components\",\n\tLong:  withPreviewNote(\"The logs command displays formatted logs from various Flux components.\"),\n\tExample: `  # Print the reconciliation logs of all Flux custom resources in your cluster\n  flux logs --all-namespaces\n  \n  # Print all logs of all Flux custom resources newer than 2 minutes\n  flux logs --all-namespaces --since=2m\n\n  # Stream logs for a particular log level\n  flux logs --follow --level=error --all-namespaces\n\n  # Filter logs by kind, name and namespace\n  flux logs --kind=Kustomization --name=podinfo --namespace=default\n\n  # Print logs when Flux is installed in a different namespace than flux-system\n  flux logs --flux-namespace=my-namespace\n    `,\n\tRunE: logsCmdRun,\n}\n\ntype logsFlags struct {\n\tlogLevel      flags.LogLevel\n\tfollow        bool\n\ttail          int64\n\tkind          string\n\tname          string\n\tfluxNamespace string\n\tallNamespaces bool\n\tsinceTime     string\n\tsinceDuration time.Duration\n}\n\nvar logsArgs = logsFlags{\n\ttail: -1,\n}\n\nconst controllerContainer = \"manager\"\n\nfunc init() {\n\tlogsCmd.Flags().Var(&logsArgs.logLevel, \"level\", logsArgs.logLevel.Description())\n\tlogsCmd.Flags().StringVarP(&logsArgs.kind, \"kind\", \"\", logsArgs.kind, \"displays errors of a particular toolkit kind e.g GitRepository\")\n\tlogsCmd.Flags().StringVarP(&logsArgs.name, \"name\", \"\", logsArgs.name, \"specifies the name of the object logs to be displayed\")\n\tlogsCmd.Flags().BoolVarP(&logsArgs.follow, \"follow\", \"f\", logsArgs.follow, \"specifies if the logs should be streamed\")\n\tlogsCmd.Flags().Int64VarP(&logsArgs.tail, \"tail\", \"\", logsArgs.tail, \"lines of recent log file to display\")\n\tlogsCmd.Flags().StringVarP(&logsArgs.fluxNamespace, \"flux-namespace\", \"\", rootArgs.defaults.Namespace, \"the namespace where the Flux components are running\")\n\tlogsCmd.Flags().BoolVarP(&logsArgs.allNamespaces, \"all-namespaces\", \"A\", false, \"displays logs for objects across all namespaces\")\n\tlogsCmd.Flags().DurationVar(&logsArgs.sinceDuration, \"since\", logsArgs.sinceDuration, \"Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.\")\n\tlogsCmd.Flags().StringVar(&logsArgs.sinceTime, \"since-time\", logsArgs.sinceTime, \"Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.\")\n\trootCmd.AddCommand(logsCmd)\n}\n\nfunc logsCmdRun(cmd *cobra.Command, args []string) error {\n\tfluxSelector := fmt.Sprintf(\"%s=%s\", manifestgen.PartOfLabelKey, manifestgen.PartOfLabelValue)\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tcfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclientset, err := kubernetes.NewForConfig(cfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(args) > 0 {\n\t\treturn fmt.Errorf(\"no argument required\")\n\t}\n\n\tpods, err := getPods(ctx, clientset, logsArgs.fluxNamespace, fluxSelector)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogOpts := &corev1.PodLogOptions{\n\t\tFollow: logsArgs.follow,\n\t}\n\n\tif logsArgs.tail > -1 {\n\t\tlogOpts.TailLines = &logsArgs.tail\n\t}\n\n\tif len(logsArgs.sinceTime) > 0 && logsArgs.sinceDuration != 0 {\n\t\treturn fmt.Errorf(\"at most one of `sinceTime` or `sinceDuration` may be specified\")\n\t}\n\n\tif len(logsArgs.sinceTime) > 0 {\n\t\tt, err := util.ParseRFC3339(logsArgs.sinceTime, metav1.Now)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%s is not a valid (RFC3339) time\", logsArgs.sinceTime)\n\t\t}\n\t\tlogOpts.SinceTime = &t\n\t}\n\n\tif logsArgs.sinceDuration != 0 {\n\t\t// round up to the nearest second\n\t\tsec := int64(logsArgs.sinceDuration.Round(time.Second).Seconds())\n\t\tlogOpts.SinceSeconds = &sec\n\t}\n\n\tvar requests []rest.ResponseWrapper\n\tfor _, pod := range pods {\n\t\tlogOpts := logOpts.DeepCopy()\n\t\tif len(pod.Spec.Containers) > 1 {\n\t\t\tlogOpts.Container = controllerContainer\n\t\t}\n\t\treq := clientset.CoreV1().Pods(logsArgs.fluxNamespace).GetLogs(pod.Name, logOpts)\n\t\trequests = append(requests, req)\n\t}\n\n\tif logsArgs.follow && len(requests) > 1 {\n\t\treturn parallelPodLogs(ctx, requests)\n\t}\n\n\treturn podLogs(ctx, requests)\n}\n\n// getPods searches for all Deployments in the given namespace that match the given label and returns a list of Pods\n// from these Deployments. For each Deployment a single Pod is chosen (based on various factors such as the running\n// state). If no Pod is found, an error is returned.\nfunc getPods(ctx context.Context, c *kubernetes.Clientset, ns string, label string) ([]corev1.Pod, error) {\n\tvar ret []corev1.Pod\n\n\topts := metav1.ListOptions{\n\t\tLabelSelector: label,\n\t}\n\tdeployList, err := c.AppsV1().Deployments(ns).List(ctx, opts)\n\tif err != nil {\n\t\treturn ret, err\n\t}\n\n\tfor _, deploy := range deployList.Items {\n\t\tlabel := deploy.Spec.Template.Labels\n\t\topts := metav1.ListOptions{\n\t\t\tLabelSelector: createLabelStringFromMap(label),\n\t\t}\n\t\tpodList, err := c.CoreV1().Pods(ns).List(ctx, opts)\n\t\tif err != nil {\n\t\t\treturn ret, err\n\t\t}\n\t\tpods := []*corev1.Pod{}\n\t\tfor i := range podList.Items {\n\t\t\tpod := podList.Items[i]\n\t\t\tpods = append(pods, &pod)\n\t\t}\n\n\t\tif len(pods) > 0 {\n\t\t\t// sort pods to prioritize running pods over others\n\t\t\tsort.Sort(podutils.ByLogging(pods))\n\t\t\tret = append(ret, *pods[0])\n\t\t}\n\t}\n\n\tif len(ret) == 0 {\n\t\treturn nil, fmt.Errorf(\"no Flux pods found in namespace %q\", ns)\n\t}\n\n\treturn ret, nil\n}\n\nfunc parallelPodLogs(ctx context.Context, requests []rest.ResponseWrapper) error {\n\treader, writer := io.Pipe()\n\terrReader, errWriter := io.Pipe()\n\twg := &sync.WaitGroup{}\n\twg.Add(len(requests))\n\n\tfor _, request := range requests {\n\t\tgo func(req rest.ResponseWrapper) {\n\t\t\tdefer wg.Done()\n\t\t\tif err := logRequest(ctx, req, writer); err != nil {\n\t\t\t\tfmt.Fprintf(errWriter, \"failed getting logs: %s\\n\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}(request)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\twriter.Close()\n\t\terrWriter.Close()\n\t}()\n\n\tstdoutErrCh := asyncCopy(os.Stdout, reader)\n\tstderrErrCh := asyncCopy(os.Stderr, errReader)\n\n\treturn errors.Join(<-stdoutErrCh, <-stderrErrCh)\n}\n\n// asyncCopy copies all data from dst to src asynchronously and returns a channel for reading an error value.\n// This is basically an asynchronous wrapper around `io.Copy`. The returned channel is unbuffered and always is sent\n// a value (either nil or the error from `io.Copy`) as soon as `io.Copy` returns.\n// This function lets you copy from multiple sources into multiple destinations in parallel.\nfunc asyncCopy(dst io.Writer, src io.Reader) <-chan error {\n\terrCh := make(chan error)\n\tgo func(errCh chan error) {\n\t\t_, err := io.Copy(dst, src)\n\t\terrCh <- err\n\t}(errCh)\n\n\treturn errCh\n}\n\nfunc podLogs(ctx context.Context, requests []rest.ResponseWrapper) error {\n\tvar retErr error\n\tfor _, req := range requests {\n\t\tif err := logRequest(ctx, req, os.Stdout); err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"failed getting logs: %s\\n\", err)\n\t\t\tretErr = fmt.Errorf(\"failed to collect logs from all Flux pods\")\n\t\t\tcontinue\n\t\t}\n\t}\n\n\treturn retErr\n}\n\nfunc createLabelStringFromMap(m map[string]string) string {\n\tvar strArr []string\n\tfor key, val := range m {\n\t\tpair := fmt.Sprintf(\"%v=%v\", key, val)\n\t\tstrArr = append(strArr, pair)\n\t}\n\n\treturn strings.Join(strArr, \",\")\n}\n\nfunc logRequest(ctx context.Context, request rest.ResponseWrapper, w io.Writer) error {\n\tstream, err := request.Stream(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer stream.Close()\n\n\tscanner := bufio.NewScanner(stream)\n\n\tconst logTmpl = \"{{.Timestamp}} {{.Level}} {{or .Kind .ControllerKind}}{{if .Name}}/{{.Name}}.{{.Namespace}}{{end}} - {{.Message}} {{.Error}}\\n\"\n\tt, err := template.New(\"log\").Parse(logTmpl)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create template, err: %s\", err)\n\t}\n\n\tbw := bufio.NewWriter(w)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tif !strings.HasPrefix(line, \"{\") {\n\t\t\tcontinue\n\t\t}\n\t\tvar l ControllerLogEntry\n\t\tif err := json.Unmarshal([]byte(line), &l); err != nil {\n\t\t\tlogger.Failuref(\"parse error: %s\", err)\n\t\t\tbreak\n\t\t}\n\t\tfilterPrintLog(t, &l, bw)\n\t\tbw.Flush()\n\t}\n\n\treturn nil\n}\n\nfunc filterPrintLog(t *template.Template, l *ControllerLogEntry, w io.Writer) {\n\tif (logsArgs.logLevel == \"\" || logsArgs.logLevel == l.Level) &&\n\t\t(logsArgs.kind == \"\" || strings.EqualFold(logsArgs.kind, l.Kind) || strings.EqualFold(logsArgs.kind, l.ControllerKind)) &&\n\t\t(logsArgs.name == \"\" || strings.EqualFold(logsArgs.name, l.Name)) &&\n\t\t(logsArgs.allNamespaces || strings.EqualFold(*kubeconfigArgs.Namespace, l.Namespace)) {\n\t\terr := t.Execute(w, l)\n\t\tif err != nil {\n\t\t\tlogger.Failuref(\"log template error: %s\", err)\n\t\t}\n\t}\n}\n\ntype ControllerLogEntry struct {\n\tTimestamp      string         `json:\"ts\"`\n\tLevel          flags.LogLevel `json:\"level\"`\n\tMessage        string         `json:\"msg\"`\n\tError          string         `json:\"error,omitempty\"`\n\tLogger         string         `json:\"logger\"`\n\tKind           string         `json:\"reconciler kind,omitempty\"`\n\tControllerKind string         `json:\"controllerKind,omitempty\"`\n\tName           string         `json:\"name,omitempty\"`\n\tNamespace      string         `json:\"namespace,omitempty\"`\n}\n"
  },
  {
    "path": "cmd/flux/logs_e2e_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestLogsNoArgs(t *testing.T) {\n\tcmd := cmdTestCase{\n\t\targs:   \"logs\",\n\t\tassert: assertSuccess(),\n\t}\n\tcmd.runTestCmd(t)\n}\n\nfunc TestLogsWrongNamespace(t *testing.T) {\n\tcmd := cmdTestCase{\n\t\targs:   \"logs --flux-namespace=default\",\n\t\tassert: assertError(`no Flux pods found in namespace \"default\"`),\n\t}\n\tcmd.runTestCmd(t)\n}\n\nfunc TestLogsAllNamespaces(t *testing.T) {\n\tcmd := cmdTestCase{\n\t\targs:   \"logs --all-namespaces\",\n\t\tassert: assertSuccess(),\n\t}\n\tcmd.runTestCmd(t)\n}\n\nfunc TestLogsSince(t *testing.T) {\n\tcmd := cmdTestCase{\n\t\targs:   \"logs --since=2m\",\n\t\tassert: assertSuccess(),\n\t}\n\tcmd.runTestCmd(t)\n}\n\nfunc TestLogsSinceInvalid(t *testing.T) {\n\tcmd := cmdTestCase{\n\t\targs:   \"logs --since=XXX\",\n\t\tassert: assertError(`invalid argument \"XXX\" for \"--since\" flag: time: invalid duration \"XXX\"`),\n\t}\n\tcmd.runTestCmd(t)\n}\n\nfunc TestLogsSinceTime(t *testing.T) {\n\tcmd := cmdTestCase{\n\t\targs:   \"logs --since-time=2021-08-06T14:26:25.546Z\",\n\t\tassert: assertSuccess(),\n\t}\n\tcmd.runTestCmd(t)\n}\n\nfunc TestLogsSinceTimeInvalid(t *testing.T) {\n\tcmd := cmdTestCase{\n\t\targs:   \"logs --since-time=XXX\",\n\t\tassert: assertError(\"XXX is not a valid (RFC3339) time\"),\n\t}\n\tcmd.runTestCmd(t)\n}\n\nfunc TestLogsSinceOnlyOneAllowed(t *testing.T) {\n\tcmd := cmdTestCase{\n\t\targs:   \"logs --since=2m --since-time=2021-08-06T14:26:25.546Z\",\n\t\tassert: assertError(\"at most one of `sinceTime` or `sinceDuration` may be specified\"),\n\t}\n\tcmd.runTestCmd(t)\n}\n"
  },
  {
    "path": "cmd/flux/logs_unit_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestLogRequest(t *testing.T) {\n\tmapper := &testResponseMapper{}\n\ttests := []struct {\n\t\tname       string\n\t\tnamespace  string\n\t\tflags      logsFlags\n\t\tassertFile string\n\t}{\n\t\t{\n\t\t\tname: \"all logs\",\n\t\t\tflags: logsFlags{\n\t\t\t\ttail:          -1,\n\t\t\t\tallNamespaces: true,\n\t\t\t},\n\t\t\tassertFile: \"testdata/logs/all-logs.txt\",\n\t\t},\n\t\t{\n\t\t\tname:      \"filter by namespace\",\n\t\t\tnamespace: \"default\",\n\t\t\tflags: logsFlags{\n\t\t\t\ttail: -1,\n\t\t\t},\n\t\t\tassertFile: \"testdata/logs/namespace.txt\",\n\t\t},\n\t\t{\n\t\t\tname: \"filter by kind and namespace\",\n\t\t\tflags: logsFlags{\n\t\t\t\ttail: -1,\n\t\t\t\tkind: \"Kustomization\",\n\t\t\t},\n\t\t\tassertFile: \"testdata/logs/kind.txt\",\n\t\t},\n\t\t{\n\t\t\tname: \"filter by loglevel\",\n\t\t\tflags: logsFlags{\n\t\t\t\ttail:          -1,\n\t\t\t\tlogLevel:      \"error\",\n\t\t\t\tallNamespaces: true,\n\t\t\t},\n\t\t\tassertFile: \"testdata/logs/log-level.txt\",\n\t\t},\n\t\t{\n\t\t\tname:      \"filter by namespace, name, loglevel and kind\",\n\t\t\tnamespace: \"flux-system\",\n\t\t\tflags: logsFlags{\n\t\t\t\ttail:     -1,\n\t\t\t\tlogLevel: \"error\",\n\t\t\t\tkind:     \"Kustomization\",\n\t\t\t\tname:     \"podinfo\",\n\t\t\t},\n\t\t\tassertFile: \"testdata/logs/multiple-filters.txt\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tg := NewWithT(t)\n\n\t\t\tlogsArgs = tt.flags\n\t\t\tif tt.namespace != \"\" {\n\t\t\t\t*kubeconfigArgs.Namespace = tt.namespace\n\t\t\t}\n\t\t\tw := bytes.NewBuffer([]byte{})\n\t\t\terr := logRequest(context.Background(), mapper, w)\n\t\t\tg.Expect(err).To(BeNil())\n\n\t\t\tgot := make([]byte, w.Len())\n\t\t\t_, err = w.Read(got)\n\t\t\tg.Expect(err).To(BeNil())\n\n\t\t\texpected, err := os.ReadFile(tt.assertFile)\n\t\t\tg.Expect(err).To(BeNil())\n\n\t\t\tg.Expect(string(got)).To(Equal(string(expected)))\n\n\t\t\t// reset flags to default\n\t\t\t*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace\n\t\t\tlogsArgs = logsFlags{\n\t\t\t\ttail: -1,\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar testPodLogs = `{\"level\":\"info\",\"ts\":\"2022-08-02T12:55:34.419Z\",\"msg\":\"no changes since last reconcilation: observed revision\",\"controller\":\"gitrepository\",\"controllerGroup\":\"source.toolkit.fluxcd.io\",\"controllerKind\":\"GitRepository\",\"gitRepository\":{\"name\":\"podinfo\",\"namespace\":\"default\"},\"namespace\":\"default\",\"name\":\"podinfo\",\"reconcileID\":\"5ef9b2ef-4ea5-47b7-b887-a247cafc1bce\"}\n{\"level\":\"error\",\"ts\":\"2022-08-02T12:56:04.679Z\",\"logger\":\"controller.gitrepository\",\"msg\":\"no changes since last reconcilation: observed revision\",\"controllerGroup\":\"source.toolkit.fluxcd.io\",\"controllerKind\":\"GitRepository\",\"gitRepository\":{\"name\":\"podinfo\",\"namespace\":\"flux-system\"},\"name\":\"flux-system\",\"namespace\":\"flux-system\",\"reconcileID\":\"543ef9b2ef-4ea5-47b7-b887-a247cafc1bce\"}\n{\"level\":\"error\",\"ts\":\"2022-08-02T12:56:34.961Z\",\"logger\":\"controller.kustomization\",\"msg\":\"no changes since last reconcilation: observed revision\",\"reconciler group\":\"kustomize.toolkit.fluxcd.io\",\"reconciler kind\":\"Kustomization\",\"name\":\"flux-system\",\"namespace\":\"flux-system\"}\n{\"level\":\"info\",\"ts\":\"2022-08-02T12:56:34.961Z\",\"logger\":\"controller.kustomization\",\"msg\":\"no changes since last reconcilation: observed revision\",\"reconciler group\":\"kustomize.toolkit.fluxcd.io\",\"reconciler kind\":\"Kustomization\",\"name\":\"podinfo\",\"namespace\":\"default\"}\n{\"level\":\"info\",\"ts\":\"2022-08-02T12:56:34.961Z\",\"logger\":\"controller.gitrepository\",\"msg\":\"no changes since last reconcilation: observed revision\",\"reconciler group\":\"source.toolkit.fluxcd.io\",\"reconciler kind\":\"GitRepository\",\"name\":\"podinfo\",\"namespace\":\"default\"}\n{\"level\":\"error\",\"ts\":\"2022-08-02T12:56:34.961Z\",\"logger\":\"controller.kustomization\",\"msg\":\"no changes since last reconcilation: observed revision\",\"reconciler group\":\"kustomize.toolkit.fluxcd.io\",\"reconciler kind\":\"Kustomization\",\"name\":\"podinfo\",\"namespace\":\"flux-system\"}\n`\n\ntype testResponseMapper struct {\n}\n\nfunc (t *testResponseMapper) DoRaw(_ context.Context) ([]byte, error) {\n\treturn nil, nil\n}\n\nfunc (t *testResponseMapper) Stream(_ context.Context) (io.ReadCloser, error) {\n\treturn io.NopCloser(strings.NewReader(testPodLogs)), nil\n}\n"
  },
  {
    "path": "cmd/flux/main.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/term\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\tctrllog \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\trunclient \"github.com/fluxcd/pkg/runtime/client\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n)\n\nvar VERSION = \"0.0.0-dev.0\"\n\nvar rootCmd = &cobra.Command{\n\tUse:           \"flux\",\n\tVersion:       VERSION,\n\tSilenceUsage:  true,\n\tSilenceErrors: true,\n\tShort:         \"Command line utility for assembling Kubernetes CD pipelines\",\n\tLong: `\nCommand line utility for assembling Kubernetes CD pipelines the GitOps way.`,\n\tExample: `  # Check prerequisites\n  flux check --pre\n\n  # Install the latest version of Flux\n  flux install\n\n  # Create a source for a public Git repository\n  flux create source git webapp-latest \\\n    --url=https://github.com/stefanprodan/podinfo \\\n    --branch=master \\\n    --interval=3m\n\n  # List GitRepository sources and their status\n  flux get sources git\n\n  # Trigger a GitRepository source reconciliation\n  flux reconcile source git flux-system\n\n  # Export GitRepository sources in YAML format\n  flux export source git --all > sources.yaml\n\n  # Create a Kustomization for deploying a series of microservices\n  flux create kustomization webapp-dev \\\n    --source=webapp-latest \\\n    --path=\"./deploy/webapp/\" \\\n    --prune=true \\\n    --interval=5m \\\n    --health-check=\"Deployment/backend.webapp\" \\\n    --health-check=\"Deployment/frontend.webapp\" \\\n    --health-check-timeout=2m\n\n  # Trigger a git sync of the Kustomization's source and apply changes\n  flux reconcile kustomization webapp-dev --with-source\n\n  # Suspend a Kustomization reconciliation\n  flux suspend kustomization webapp-dev\n\n  # Export Kustomizations in YAML format\n  flux export kustomization --all > kustomizations.yaml\n\n  # Resume a Kustomization reconciliation\n  flux resume kustomization webapp-dev\n\n  # Delete a Kustomization\n  flux delete kustomization webapp-dev\n\n  # Delete a GitRepository source\n  flux delete source git webapp-latest\n\n  # Uninstall Flux and delete CRDs\n  flux uninstall`,\n\tPersistentPreRunE: func(cmd *cobra.Command, args []string) error {\n\t\tns, err := cmd.Flags().GetString(\"namespace\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error getting namespace: %w\", err)\n\t\t}\n\n\t\tif e := validation.IsDNS1123Label(ns); len(e) > 0 {\n\t\t\treturn fmt.Errorf(\"namespace must be a valid DNS label: %q\", ns)\n\t\t}\n\n\t\treturn nil\n\t},\n}\n\nvar logger = stderrLogger{stderr: os.Stderr}\n\ntype rootFlags struct {\n\ttimeout      time.Duration\n\tverbose      bool\n\tpollInterval time.Duration\n\tdefaults     install.Options\n}\n\n// RequestError is a custom error type that wraps an error returned by the flux api.\ntype RequestError struct {\n\tStatusCode int\n\tErr        error\n}\n\nfunc (r *RequestError) Error() string {\n\treturn r.Err.Error()\n}\n\nvar rootArgs = NewRootFlags()\nvar kubeconfigArgs = genericclioptions.NewConfigFlags(false)\nvar kubeclientOptions = new(runclient.Options)\n\nfunc init() {\n\trootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, \"timeout\", 5*time.Minute, \"timeout for this operation\")\n\trootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, \"verbose\", false, \"print generated objects\")\n\n\tconfigureDefaultNamespace()\n\tkubeconfigArgs.APIServer = nil // prevent AddFlags from configuring --server flag\n\tkubeconfigArgs.Timeout = nil   // prevent AddFlags from configuring --request-timeout flag, we have --timeout instead\n\tkubeconfigArgs.AddFlags(rootCmd.PersistentFlags())\n\n\t// Since some subcommands use the `-s` flag as a short version for `--silent`, we manually configure the server flag\n\t// without the `-s` short version. While we're no longer on par with kubectl's flags, we maintain backwards compatibility\n\t// on the CLI interface.\n\tapiServer := \"\"\n\tkubeconfigArgs.APIServer = &apiServer\n\trootCmd.PersistentFlags().StringVar(kubeconfigArgs.APIServer, \"server\", *kubeconfigArgs.APIServer, \"The address and port of the Kubernetes API server\")\n\t// Update the description for kubeconfig TLS flags so that user's don't mistake it for a Flux specific flag\n\trootCmd.Flag(\"insecure-skip-tls-verify\").Usage = \"If true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure\"\n\trootCmd.Flag(\"client-certificate\").Usage = \"Path to a client certificate file for TLS authentication to the Kubernetes API server\"\n\trootCmd.Flag(\"certificate-authority\").Usage = \"Path to a cert file for the certificate authority to authenticate the Kubernetes API server\"\n\trootCmd.Flag(\"client-key\").Usage = \"Path to a client key file for TLS authentication to the Kubernetes API server\"\n\n\tkubeclientOptions.BindFlags(rootCmd.PersistentFlags())\n\n\trootCmd.RegisterFlagCompletionFunc(\"context\", contextsCompletionFunc)\n\trootCmd.RegisterFlagCompletionFunc(\"namespace\", resourceNamesCompletionFunc(corev1.SchemeGroupVersion.WithKind(\"Namespace\")))\n\n\trootCmd.DisableAutoGenTag = true\n\trootCmd.SetOut(os.Stdout)\n}\n\nfunc NewRootFlags() rootFlags {\n\trf := rootFlags{\n\t\tpollInterval: 2 * time.Second,\n\t\tdefaults:     install.MakeDefaultOptions(),\n\t}\n\trf.defaults.Version = \"v\" + VERSION\n\treturn rf\n}\n\nfunc main() {\n\tlog.SetFlags(0)\n\n\t// This is required because controller-runtime expects its consumers to\n\t// set a logger through log.SetLogger within 30 seconds of the program's\n\t// initialization. If not set, the entire debug stack is printed as an\n\t// error, see: https://github.com/kubernetes-sigs/controller-runtime/blob/ed8be90/pkg/log/log.go#L59\n\t// Since we have our own logging and don't care about controller-runtime's\n\t// logger, we configure it's logger to do nothing.\n\tctrllog.SetLogger(logr.New(ctrllog.NullLogSink{}))\n\n\tif err := rootCmd.Execute(); err != nil {\n\n\t\tif err, ok := err.(*RequestError); ok {\n\t\t\tif err.StatusCode == 1 {\n\t\t\t\tlogger.Warningf(\"%v\", err)\n\t\t\t} else {\n\t\t\t\tlogger.Failuref(\"%v\", err)\n\t\t\t}\n\n\t\t\tos.Exit(err.StatusCode)\n\t\t}\n\n\t\tlogger.Failuref(\"%v\", err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc configureDefaultNamespace() {\n\t*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace\n\tfromEnv := os.Getenv(\"FLUX_SYSTEM_NAMESPACE\")\n\tif fromEnv != \"\" {\n\t\t// namespace must be a valid DNS label. Assess against validation\n\t\t// used upstream, and ignore invalid values as environment vars\n\t\t// may not be actively provided by end-user.\n\t\tif e := validation.IsDNS1123Label(fromEnv); len(e) > 0 {\n\t\t\tlogger.Warningf(\" ignoring invalid FLUX_SYSTEM_NAMESPACE: %q\", fromEnv)\n\t\t\treturn\n\t\t}\n\n\t\tkubeconfigArgs.Namespace = &fromEnv\n\t}\n}\n\n// readPasswordFromStdin reads a password from stdin and returns the input\n// with trailing newline and/or carriage return removed. It also makes sure that terminal\n// echoing is turned off if stdin is a terminal.\nfunc readPasswordFromStdin(prompt string) (string, error) {\n\tvar out string\n\tvar err error\n\tif _, err := fmt.Fprint(os.Stdout, prompt); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to write prompt: %w\", err)\n\t}\n\tstdinFD := int(os.Stdin.Fd())\n\tif term.IsTerminal(stdinFD) {\n\t\tvar inBytes []byte\n\t\tinBytes, err = term.ReadPassword(int(os.Stdin.Fd()))\n\t\tout = string(inBytes)\n\t} else {\n\t\tout, err = bufio.NewReader(os.Stdin).ReadString('\\n')\n\t}\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"could not read from stdin: %w\", err)\n\t}\n\tfmt.Println()\n\treturn strings.TrimRight(out, \"\\r\\n\"), nil\n}\n\nfunc withPreviewNote(desc string) string {\n\tpreviewNote := `⚠️  Please note that this command is in preview and under development.\nWhile we try our best to not introduce breaking changes, they may occur when\nwe adapt to new features and/or find better ways to facilitate what it does.`\n\treturn fmt.Sprintf(\"%s\\n\\n%s\", strings.TrimSpace(desc), previewNote)\n}\n\n// printlnStdout prints the given text to stdout with a newline.\nfunc printlnStdout(txt string) {\n\t_, _ = rootCmd.OutOrStdout().Write([]byte(txt + \"\\n\"))\n}\n"
  },
  {
    "path": "cmd/flux/main_e2e_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/go-logr/logr\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nfunc TestMain(m *testing.M) {\n\tlog.SetLogger(logr.New(log.NullLogSink{}))\n\n\t// Ensure tests print consistent timestamps regardless of timezone\n\tos.Setenv(\"TZ\", \"UTC\")\n\n\ttestEnv, err := NewTestEnvKubeManager(ExistingClusterMode)\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"error creating kube manager: '%w'\", err))\n\t}\n\tkubeconfigArgs.KubeConfig = &testEnv.kubeConfigPath\n\n\t// Install Flux.\n\toutput, err := executeCommand(\"install --components-extra=image-reflector-controller,image-automation-controller\")\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"install failed: %s error:'%w'\", output, err))\n\t}\n\n\t// Run tests\n\tcode := m.Run()\n\n\t// Uninstall Flux\n\toutput, err = executeCommand(\"uninstall -s --keep-namespace\")\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"uninstall failed: %s error:'%w'\", output, err))\n\t}\n\n\t// Delete namespace and wait for finalisation\n\tkubectlArgs := []string{\"delete\", \"namespace\", \"flux-system\"}\n\t_, err = utils.ExecKubectlCommand(context.TODO(), utils.ModeStderrOS, *kubeconfigArgs.KubeConfig, *kubeconfigArgs.Context, kubectlArgs...)\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"delete namespace error:'%w'\", err))\n\t}\n\n\ttestEnv.Stop()\n\n\tos.Exit(code)\n}\n\nfunc execSetupTestNamespace(namespace string) (func(), error) {\n\tkubectlArgs := []string{\"create\", \"namespace\", namespace}\n\t_, err := utils.ExecKubectlCommand(context.TODO(), utils.ModeStderrOS, *kubeconfigArgs.KubeConfig, *kubeconfigArgs.Context, kubectlArgs...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn func() {\n\t\tkubectlArgs := []string{\"delete\", \"namespace\", namespace}\n\t\tutils.ExecKubectlCommand(context.TODO(), utils.ModeCapture, *kubeconfigArgs.KubeConfig, *kubeconfigArgs.Context, kubectlArgs...)\n\t}, nil\n}\n"
  },
  {
    "path": "cmd/flux/main_test.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/mattn/go-shellwords\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\tk8syaml \"k8s.io/apimachinery/pkg/util/yaml\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar nextNamespaceId int64\n\n// update allows golden files to be updated based on the current output.\nvar update = flag.Bool(\"update\", false, \"update golden files\")\n\n// Return a unique namespace with the specified prefix, for tests to create\n// objects that won't collide with each other.\nfunc allocateNamespace(prefix string) string {\n\tid := atomic.AddInt64(&nextNamespaceId, 1)\n\treturn fmt.Sprintf(\"%s-%d\", prefix, id)\n}\n\nfunc readYamlObjects(rdr io.Reader) ([]*unstructured.Unstructured, error) {\n\tobjects := []*unstructured.Unstructured{}\n\treader := k8syaml.NewYAMLReader(bufio.NewReader(rdr))\n\tfor {\n\t\tdoc, err := reader.Read()\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tunstructuredObj := &unstructured.Unstructured{}\n\t\tdecoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(doc), len(doc))\n\t\terr = decoder.Decode(unstructuredObj)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tobjects = append(objects, unstructuredObj)\n\t}\n\treturn objects, nil\n}\n\n// A KubeManager that can create objects that are subject to a test.\ntype testEnvKubeManager struct {\n\tclient         client.WithWatch\n\ttestEnv        *envtest.Environment\n\tkubeConfigPath string\n}\n\nfunc (m *testEnvKubeManager) CreateObjectFile(objectFile string, templateValues map[string]string, t *testing.T) {\n\tbuf, err := os.ReadFile(objectFile)\n\tif err != nil {\n\t\tt.Fatalf(\"Error reading file '%s': %v\", objectFile, err)\n\t}\n\tcontent, err := executeTemplate(string(buf), templateValues)\n\tif err != nil {\n\t\tt.Fatalf(\"Error evaluating template file '%s': '%v'\", objectFile, err)\n\t}\n\tclientObjects, err := readYamlObjects(strings.NewReader(content))\n\tif err != nil {\n\t\tt.Fatalf(\"Error decoding yaml file '%s': %v\", objectFile, err)\n\t}\n\terr = m.CreateObjects(clientObjects, t)\n\tif err != nil {\n\t\tt.Logf(\"Error creating test objects: '%v'\", err)\n\t}\n}\n\nfunc (m *testEnvKubeManager) CreateObjects(clientObjects []*unstructured.Unstructured, t *testing.T) error {\n\tfor _, obj := range clientObjects {\n\t\t// First create the object then set its status if present in the\n\t\t// yaml file. Make a copy first since creating an object may overwrite\n\t\t// the status.\n\t\tcreateObj := obj.DeepCopy()\n\t\terr := m.client.Create(context.Background(), createObj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tobj.SetResourceVersion(createObj.GetResourceVersion())\n\t\terr = m.client.Status().Update(context.Background(), obj)\n\t\t// Updating status of static objects results in not found error.\n\t\tif err != nil && !errors.IsNotFound(err) {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *testEnvKubeManager) DeleteObjectFile(objectFile string, templateValues map[string]string, t *testing.T) {\n\tbuf, err := os.ReadFile(objectFile)\n\tif err != nil {\n\t\tt.Fatalf(\"Error reading file '%s': %v\", objectFile, err)\n\t}\n\tcontent, err := executeTemplate(string(buf), templateValues)\n\tif err != nil {\n\t\tt.Fatalf(\"Error evaluating template file '%s': '%v'\", objectFile, err)\n\t}\n\tclientObjects, err := readYamlObjects(strings.NewReader(content))\n\tif err != nil {\n\t\tt.Fatalf(\"Error decoding yaml file '%s': %v\", objectFile, err)\n\t}\n\terr = m.DeleteObjects(clientObjects, t)\n\tif err != nil {\n\t\tt.Logf(\"Error deleting test objects: '%v'\", err)\n\t}\n}\n\nfunc (m *testEnvKubeManager) DeleteObjects(clientObjects []*unstructured.Unstructured, t *testing.T) error {\n\tfor _, obj := range clientObjects {\n\t\terr := m.client.Delete(context.Background(), obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (m *testEnvKubeManager) Stop() error {\n\tif m.testEnv == nil {\n\t\treturn fmt.Errorf(\"do nothing because testEnv is nil\")\n\t}\n\treturn m.testEnv.Stop()\n}\n\nfunc NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager, error) {\n\tswitch testClusterMode {\n\tcase TestEnvClusterMode:\n\t\tuseExistingCluster := false\n\t\ttestEnv := &envtest.Environment{\n\t\t\tUseExistingCluster: &useExistingCluster,\n\t\t\tCRDDirectoryPaths:  []string{\"manifests\"},\n\t\t}\n\t\tcfg, err := testEnv.Start()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tuser, err := testEnv.ControlPlane.AddUser(envtest.User{\n\t\t\tName:   \"envtest-admin\",\n\t\t\tGroups: []string{\"system:masters\"},\n\t\t}, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tkubeConfig, err := user.KubeConfig()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ttmpFilename := filepath.Join(\"/tmp\", \"kubeconfig-\"+time.Nanosecond.String())\n\t\tos.WriteFile(tmpFilename, kubeConfig, 0o600)\n\t\tk8sClient, err := client.NewWithWatch(cfg, client.Options{\n\t\t\tScheme: utils.NewScheme(),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &testEnvKubeManager{\n\t\t\ttestEnv:        testEnv,\n\t\t\tclient:         k8sClient,\n\t\t\tkubeConfigPath: tmpFilename,\n\t\t}, nil\n\tcase ExistingClusterMode:\n\t\t// TEST_KUBECONFIG is mandatory to prevent destroying a current cluster accidentally.\n\t\ttestKubeConfig := os.Getenv(\"TEST_KUBECONFIG\")\n\t\tif testKubeConfig == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"environment variable TEST_KUBECONFIG is required to run tests against an existing cluster\")\n\t\t}\n\n\t\tuseExistingCluster := true\n\t\tconfig, err := clientcmd.BuildConfigFromFlags(\"\", testKubeConfig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttestEnv := &envtest.Environment{\n\t\t\tUseExistingCluster: &useExistingCluster,\n\t\t\tConfig:             config,\n\t\t}\n\t\tcfg, err := testEnv.Start()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tk8sClient, err := client.NewWithWatch(cfg, client.Options{\n\t\t\tScheme: utils.NewScheme(),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &testEnvKubeManager{\n\t\t\ttestEnv:        testEnv,\n\t\t\tclient:         k8sClient,\n\t\t\tkubeConfigPath: testKubeConfig,\n\t\t}, nil\n\t}\n\n\treturn nil, nil\n}\n\n// Function that sets an expectation on the output of a command. Tests can\n// either implement this directly or use a helper below.\ntype assertFunc func(output string, err error) error\n\n// Assemble multiple assertFuncs into a single assertFunc\nfunc assert(fns ...assertFunc) assertFunc {\n\treturn func(output string, err error) error {\n\t\tfor _, fn := range fns {\n\t\t\tif assertErr := fn(output, err); assertErr != nil {\n\t\t\t\treturn assertErr\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// Expect the command to run without error\nfunc assertSuccess() assertFunc {\n\treturn func(output string, err error) error {\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Expected success but was error: %v\", err)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// Expect the command to fail with the specified error\nfunc assertError(expected string) assertFunc {\n\treturn func(output string, err error) error {\n\t\tif err == nil {\n\t\t\treturn fmt.Errorf(\"Expected error but was success\")\n\t\t}\n\t\tif expected != err.Error() {\n\t\t\treturn fmt.Errorf(\"Expected error '%v' but got '%v'\", expected, err.Error())\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// Expect the command to succeed with the expected test output.\nfunc assertGoldenValue(expected string) assertFunc {\n\treturn assert(\n\t\tassertSuccess(),\n\t\tfunc(output string, err error) error {\n\t\t\tdiff := cmp.Diff(expected, output)\n\t\t\tif diff != \"\" {\n\t\t\t\treturn fmt.Errorf(\"Mismatch from expected value (-want +got):\\n%s\", diff)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n}\n\n// Filename that contains the expected test output.\nfunc assertGoldenFile(goldenFile string) assertFunc {\n\treturn assertGoldenTemplateFile(goldenFile, map[string]string{})\n}\n\n// Filename that contains the expected test output. The golden file is a template that\n// is pre-processed with the specified templateValues.\nfunc assertGoldenTemplateFile(goldenFile string, templateValues map[string]string) assertFunc {\n\tgoldenFileContents, fileErr := os.ReadFile(goldenFile)\n\treturn assert(\n\t\tassertSuccess(),\n\t\tfunc(output string, err error) error {\n\t\t\tif fileErr != nil {\n\t\t\t\treturn fmt.Errorf(\"Error reading golden file '%s': %s\", goldenFile, fileErr)\n\t\t\t}\n\t\t\tvar expectedOutput string\n\t\t\tif len(templateValues) > 0 {\n\t\t\t\texpectedOutput, err = executeTemplate(string(goldenFileContents), templateValues)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"Error executing golden template file '%s': %s\", goldenFile, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\texpectedOutput = string(goldenFileContents)\n\t\t\t}\n\t\t\tif assertErr := assertGoldenValue(expectedOutput)(output, err); assertErr != nil {\n\t\t\t\t// Update the golden files if comparison fails and the update flag is set.\n\t\t\t\tif *update && output != \"\" {\n\t\t\t\t\t// Skip update if there are template values.\n\t\t\t\t\tif len(templateValues) > 0 {\n\t\t\t\t\t\tfmt.Println(\"NOTE: -update flag passed but golden template files can't be updated, please update it manually\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif err := os.WriteFile(goldenFile, []byte(output), 0o600); err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"failed to update golden file '%s': %v\", goldenFile, err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn fmt.Errorf(\"Mismatch from golden file '%s': %v\", goldenFile, assertErr)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n}\n\ntype TestClusterMode int\n\nconst (\n\tTestEnvClusterMode = TestClusterMode(iota + 1)\n\tExistingClusterMode\n)\n\n// Structure used for each test to load objects into kubernetes, run\n// commands and assert on the expected output.\ntype cmdTestCase struct {\n\t// The command line arguments to test.\n\targs string\n\t// Tests use assertFunc to assert on an output, success or failure. This\n\t// can be a function defined by the test or existing function above.\n\tassert assertFunc\n}\n\nfunc (cmd *cmdTestCase) runTestCmd(t *testing.T) {\n\tactual, testErr := executeCommand(cmd.args)\n\t// If the cmd error is a change, discard it\n\tif isChangeError(testErr) {\n\t\ttestErr = nil\n\t}\n\n\tif assertErr := cmd.assert(actual, testErr); assertErr != nil {\n\t\tt.Error(assertErr)\n\t}\n}\n\nfunc executeTemplate(content string, templateValues map[string]string) (string, error) {\n\ttmpl := template.Must(template.New(\"golden\").Parse(content))\n\tvar out bytes.Buffer\n\tif err := tmpl.Execute(&out, templateValues); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn out.String(), nil\n}\n\n// Run the command and return the captured output.\nfunc executeCommand(cmd string) (string, error) {\n\tdefer resetCmdArgs()\n\tdefer func() {\n\t\t// need to set this explicitly because apparently its value isn't changed\n\t\t// in subsequent executions which causes tests to fail that rely on the value\n\t\t// of \"Changed\".\n\t\tresumeCmd.PersistentFlags().Lookup(\"wait\").Changed = false\n\t}()\n\targs, err := shellwords.Parse(cmd)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbuf := new(bytes.Buffer)\n\n\trootCmd.SetOut(buf)\n\trootCmd.SetErr(buf)\n\trootCmd.SetArgs(args)\n\n\tlogger.stderr = rootCmd.ErrOrStderr()\n\n\t_, err = rootCmd.ExecuteC()\n\tresult := buf.String()\n\n\treturn result, err\n}\n\n// Run the command while passing the string as input and return the captured output.\nfunc executeCommandWithIn(cmd string, in io.Reader) (string, error) {\n\tdefer resetCmdArgs()\n\targs, err := shellwords.Parse(cmd)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbuf := new(bytes.Buffer)\n\n\trootCmd.SetOut(buf)\n\trootCmd.SetErr(buf)\n\trootCmd.SetArgs(args)\n\tif in != nil {\n\t\trootCmd.SetIn(in)\n\t}\n\n\t_, err = rootCmd.ExecuteC()\n\tresult := buf.String()\n\n\treturn result, err\n}\n\n// resetCmdArgs resets the flags for various cmd\n// Note: this will also clear default value of the flags set in init()\nfunc resetCmdArgs() {\n\t*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace\n\talertArgs = alertFlags{}\n\talertProviderArgs = alertProviderFlags{}\n\tbootstrapArgs = NewBootstrapFlags()\n\tbServerArgs = bServerFlags{}\n\tlogsArgs = logsFlags{\n\t\ttail:          -1,\n\t\tfluxNamespace: rootArgs.defaults.Namespace,\n\t}\n\tbuildKsArgs = buildKsFlags{\n\t\tlocalSources: map[string]string{},\n\t}\n\tcheckArgs = checkFlags{}\n\tcreateArgs = createFlags{}\n\tdeleteArgs = deleteFlags{}\n\tdiffKsArgs = diffKsFlags{}\n\texportArgs = exportFlags{}\n\tgetArgs = GetFlags{}\n\tgitArgs = gitFlags{}\n\tgithubArgs = githubFlags{}\n\tgitlabArgs = gitlabFlags{}\n\thelmReleaseArgs = helmReleaseFlags{\n\t\treconcileStrategy: \"ChartVersion\",\n\t}\n\timagePolicyArgs = imagePolicyFlags{}\n\timageRepoArgs = imageRepoFlags{}\n\timageUpdateArgs = imageUpdateFlags{}\n\tinstallArgs = newInstallFlags()\n\tkustomizationArgs = NewKustomizationFlags()\n\treceiverArgs = receiverFlags{}\n\tresumeArgs = ResumeFlags{}\n\trhrArgs = reconcileHelmReleaseFlags{}\n\trksArgs = reconcileKsFlags{}\n\tsecretGitArgs = NewSecretGitFlags()\n\tsecretGitHubAppArgs = secretGitHubAppFlags{}\n\tsecretProxyArgs = secretProxyFlags{}\n\tsecretHelmArgs = secretHelmFlags{}\n\tsecretTLSArgs = secretTLSFlags{}\n\tsourceBucketArgs = sourceBucketFlags{}\n\tsourceGitArgs = newSourceGitFlags()\n\tsourceHelmArgs = sourceHelmFlags{}\n\tsourceOCIRepositoryArgs = sourceOCIRepositoryFlags{}\n\tsuspendArgs = SuspendFlags{}\n\ttenantArgs = tenantFlags{}\n\ttraceArgs = traceFlags{}\n\ttreeKsArgs = TreeKsFlags{}\n\tuninstallArgs = uninstallFlags{}\n\tversionArgs = versionFlags{\n\t\toutput: \"yaml\",\n\t}\n\tenvsubstArgs = envsubstFlags{}\n\tdebugHelmReleaseArgs = debugHelmReleaseFlags{}\n\tdebugKustomizationArgs = debugKustomizationFlags{}\n}\n\nfunc isChangeError(err error) bool {\n\tif reqErr, ok := err.(*RequestError); ok {\n\t\tif strings.Contains(err.Error(), \"identified at least one change, exiting with non-zero exit code\") && reqErr.StatusCode == 1 {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/main_unit_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/go-logr/logr\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\n// The test environment is long running process shared between tests, initialized\n// by a `TestMain` function depending on how the test is involved and which tests\n// are a part of the build.\nvar testEnv *testEnvKubeManager\n\nfunc TestMain(m *testing.M) {\n\tlog.SetLogger(logr.New(log.NullLogSink{}))\n\n\t// Ensure tests print consistent timestamps regardless of timezone\n\tos.Setenv(\"TZ\", \"UTC\")\n\n\t// Creating the test env manager sets rootArgs client flags\n\tkm, err := NewTestEnvKubeManager(TestEnvClusterMode)\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"error creating kube manager: '%w'\", err))\n\t}\n\ttestEnv = km\n\t// rootArgs.kubeconfig = testEnv.kubeConfigPath\n\tkubeconfigArgs.KubeConfig = &testEnv.kubeConfigPath\n\n\t// Run tests\n\tcode := m.Run()\n\n\tkm.Stop()\n\n\tos.Exit(code)\n}\n\nfunc setupTestNamespace(namespace string, t *testing.T) {\n\tns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}\n\terr := testEnv.client.Create(context.Background(), ns)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create namespace: %v\", err)\n\t}\n\tt.Cleanup(func() {\n\t\t_ = testEnv.client.Delete(context.Background(), ns)\n\t})\n}\n"
  },
  {
    "path": "cmd/flux/manifests.embed.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"embed\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path\"\n)\n\n//go:embed manifests/*.yaml\nvar embeddedManifests embed.FS\n\nfunc writeEmbeddedManifests(dir string) error {\n\tmanifests, err := fs.ReadDir(embeddedManifests, \"manifests\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, manifest := range manifests {\n\t\tdata, err := fs.ReadFile(embeddedManifests, path.Join(\"manifests\", manifest.Name()))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"reading file failed: %w\", err)\n\t\t}\n\n\t\terr = os.WriteFile(path.Join(dir, manifest.Name()), data, 0666)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"writing file failed: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/migrate.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/fluxcd/pkg/ssa\"\n\t\"github.com/manifoldco/promptui\"\n\t\"github.com/spf13/cobra\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/util/retry\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\timageautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\timageautov1b2 \"github.com/fluxcd/image-automation-controller/api/v1beta2\"\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n\timagev1b2 \"github.com/fluxcd/image-reflector-controller/api/v1beta2\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n\tnotificationv1b3 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\tswv1b1 \"github.com/fluxcd/source-watcher/api/v2/v1beta1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\n// APIVersions holds the mapping of GroupKinds to their respective\n// latest API versions for a specific Flux version.\ntype APIVersions struct {\n\tFluxVersion    string\n\tLatestVersions map[schema.GroupKind]string\n}\n\n// TODO: Update this mapping when new Flux minor versions are released!\n// latestAPIVersions contains the latest API versions for each GroupKind\n// for each supported Flux version. The number of latest minor versions\n// we maintain here must match what's documented here:\n//\n// https://fluxcd.io/flux/releases/#supported-releases\nvar latestAPIVersions = []APIVersions{\n\t{\n\t\tFluxVersion:    \"2.8\",\n\t\tLatestVersions: flux27LatestAPIVersions,\n\t},\n\t{\n\t\tFluxVersion:    \"2.7\",\n\t\tLatestVersions: flux27LatestAPIVersions,\n\t},\n\t{\n\t\tFluxVersion:    \"2.6\",\n\t\tLatestVersions: flux26LatestAPIVersions,\n\t},\n}\n\nvar flux27LatestAPIVersions = map[schema.GroupKind]string{\n\t// source-controller\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.BucketKind}:           sourcev1.GroupVersion.Version,\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.GitRepositoryKind}:    sourcev1.GroupVersion.Version,\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.OCIRepositoryKind}:    sourcev1.GroupVersion.Version,\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmRepositoryKind}:   sourcev1.GroupVersion.Version,\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmChartKind}:        sourcev1.GroupVersion.Version,\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.ExternalArtifactKind}: sourcev1.GroupVersion.Version,\n\n\t// kustomize-controller\n\t{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind}: kustomizev1.GroupVersion.Version,\n\n\t// helm-controller\n\t{Group: helmv2.GroupVersion.Group, Kind: helmv2.HelmReleaseKind}: helmv2.GroupVersion.Version,\n\n\t// notification-controller\n\t{Group: notificationv1.GroupVersion.Group, Kind: notificationv1.ReceiverKind}:     notificationv1.GroupVersion.Version,\n\t{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.AlertKind}:    notificationv1b3.GroupVersion.Version,\n\t{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.ProviderKind}: notificationv1b3.GroupVersion.Version,\n\n\t// image-reflector-controller\n\t{Group: imagev1.GroupVersion.Group, Kind: imagev1.ImageRepositoryKind}: imagev1.GroupVersion.Version,\n\t{Group: imagev1.GroupVersion.Group, Kind: imagev1.ImagePolicyKind}:     imagev1.GroupVersion.Version,\n\n\t// image-automation-controller\n\t{Group: imageautov1.GroupVersion.Group, Kind: imageautov1.ImageUpdateAutomationKind}: imageautov1.GroupVersion.Version,\n\n\t// source-watcher\n\t{Group: swv1b1.GroupVersion.Group, Kind: swv1b1.ArtifactGeneratorKind}: swv1b1.GroupVersion.Version,\n}\n\nvar flux26LatestAPIVersions = map[schema.GroupKind]string{\n\t// source-controller\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.BucketKind}:           sourcev1.GroupVersion.Version,\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.GitRepositoryKind}:    sourcev1.GroupVersion.Version,\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.OCIRepositoryKind}:    sourcev1.GroupVersion.Version,\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmRepositoryKind}:   sourcev1.GroupVersion.Version,\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmChartKind}:        sourcev1.GroupVersion.Version,\n\t{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.ExternalArtifactKind}: sourcev1.GroupVersion.Version,\n\n\t// kustomize-controller\n\t{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind}: kustomizev1.GroupVersion.Version,\n\n\t// helm-controller\n\t{Group: helmv2.GroupVersion.Group, Kind: helmv2.HelmReleaseKind}: helmv2.GroupVersion.Version,\n\n\t// notification-controller\n\t{Group: notificationv1.GroupVersion.Group, Kind: notificationv1.ReceiverKind}:     notificationv1.GroupVersion.Version,\n\t{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.AlertKind}:    notificationv1b3.GroupVersion.Version,\n\t{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.ProviderKind}: notificationv1b3.GroupVersion.Version,\n\n\t// image-reflector-controller\n\t{Group: imagev1b2.GroupVersion.Group, Kind: imagev1b2.ImageRepositoryKind}: imagev1b2.GroupVersion.Version,\n\t{Group: imagev1b2.GroupVersion.Group, Kind: imagev1b2.ImagePolicyKind}:     imagev1b2.GroupVersion.Version,\n\n\t// image-automation-controller\n\t{Group: imageautov1b2.GroupVersion.Group, Kind: imageautov1b2.ImageUpdateAutomationKind}: imageautov1b2.GroupVersion.Version,\n}\n\nvar migrateCmd = &cobra.Command{\n\tUse:   \"migrate\",\n\tArgs:  cobra.NoArgs,\n\tShort: \"Migrate the Flux custom resources to their latest API version\",\n\tLong: `The migrate command must be run before a Flux minor version upgrade.\n\nThe command has two modes of operation:\n\n- Cluster mode (default): migrates all the Flux custom resources stored in Kubernetes etcd to their latest API version.\n- File system mode (-f): migrates the Flux custom resources defined in the manifests located in the specified path.\n`,\n\tExample: `  # Migrate all the Flux custom resources in the cluster.\n  # This uses the current kubeconfig context and requires cluster-admin permissions.\n  flux migrate\n\n  # Migrate all the Flux custom resources in a Git repository\n  # checked out in the current working directory.\n  flux migrate -f .\n\n  # Migrate all Flux custom resources defined in YAML and Helm YAML template files.\n  flux migrate -f . --extensions=.yml,.yaml,.tpl\n\n  # Migrate the Flux custom resources to the latest API versions of Flux 2.6.\n  flux migrate -f . --version=2.6\n\n  # Migrate the Flux custom resources defined in a multi-document YAML manifest file.\n  flux migrate -f path/to/manifest.yaml\n\n  # Simulate the migration without making any changes.\n  flux migrate -f . --dry-run\n\n  # Run the migration skipping confirmation prompts.\n  flux migrate -f . --yes\n`,\n\tRunE: runMigrateCmd,\n}\n\nvar migrateFlags struct {\n\tyes        bool\n\tdryRun     bool\n\tpath       string\n\tversion    string\n\textensions []string\n}\n\nfunc init() {\n\trootCmd.AddCommand(migrateCmd)\n\n\tmigrateCmd.Flags().StringVarP(&migrateFlags.path, \"path\", \"f\", \"\",\n\t\t\"the path to the directory containing the manifests to migrate\")\n\tmigrateCmd.Flags().StringSliceVarP(&migrateFlags.extensions, \"extensions\", \"e\", []string{\".yaml\", \".yml\"},\n\t\t\"the file extensions to consider when migrating manifests, only applicable with --path\")\n\tmigrateCmd.Flags().StringVarP(&migrateFlags.version, \"version\", \"v\", \"\",\n\t\t\"the target Flux minor version to migrate manifests to, only applicable with --path (defaults to the version of the CLI)\")\n\tmigrateCmd.Flags().BoolVarP(&migrateFlags.yes, \"yes\", \"y\", false,\n\t\t\"skip confirmation prompts when migrating manifests, only applicable with --path\")\n\tmigrateCmd.Flags().BoolVar(&migrateFlags.dryRun, \"dry-run\", false,\n\t\t\"simulate the migration of manifests without making any changes, only applicable with --path\")\n}\n\nfunc runMigrateCmd(*cobra.Command, []string) error {\n\tif migrateFlags.path == \"\" {\n\t\treturn migrateCluster()\n\t}\n\treturn migrateFileSystem()\n}\n\nfunc migrateCluster() error {\n\tlogger.Actionf(\"starting migration of custom resources\")\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tcfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"the Kubernetes client initialization failed: %w\", err)\n\t}\n\n\tkubeClient, err := client.New(cfg, client.Options{Scheme: utils.NewScheme()})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmigrator := NewClusterMigrator(kubeClient, client.MatchingLabels{\n\t\t\"app.kubernetes.io/part-of\": \"flux\",\n\t})\n\n\tif err := migrator.Run(ctx); err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Successf(\"custom resources migrated successfully\")\n\treturn nil\n}\n\nfunc migrateFileSystem() error {\n\tpathRoot, err := os.OpenRoot(\".\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open filesystem at the current working directory: %w\", err)\n\t}\n\tdefer pathRoot.Close()\n\n\tfileSystem := &osFS{pathRoot.FS()}\n\tyes := migrateFlags.yes\n\tdryRun := migrateFlags.dryRun\n\tpath := migrateFlags.path\n\textensions := migrateFlags.extensions\n\tvar latestVersions map[schema.GroupKind]string\n\n\t// Determine latest API versions based on the Flux version.\n\tif migrateFlags.version == \"\" {\n\t\tlatestVersions = latestAPIVersions[0].LatestVersions\n\t} else {\n\t\tsupportedVersions := make([]string, 0, len(latestAPIVersions))\n\t\tfor _, v := range latestAPIVersions {\n\t\t\tif v.FluxVersion == migrateFlags.version {\n\t\t\t\tlatestVersions = v.LatestVersions\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tsupportedVersions = append(supportedVersions, v.FluxVersion)\n\t\t}\n\t\tif latestVersions == nil {\n\t\t\treturn fmt.Errorf(\"version %s is not supported, supported versions are: %s\",\n\t\t\t\tmigrateFlags.version, strings.Join(supportedVersions, \", \"))\n\t\t}\n\t}\n\n\treturn NewFileSystemMigrator(fileSystem, yes, dryRun, path, extensions, latestVersions).Run()\n}\n\n// ClusterMigrator migrates all the CRs in the cluster for the CRDs matching the label selector.\ntype ClusterMigrator struct {\n\tlabelSelector client.MatchingLabels\n\tkubeClient    client.Client\n}\n\n// NewClusterMigrator creates a new ClusterMigrator instance with the specified label selector.\nfunc NewClusterMigrator(kubeClient client.Client, labelSelector client.MatchingLabels) *ClusterMigrator {\n\treturn &ClusterMigrator{\n\t\tlabelSelector: labelSelector,\n\t\tkubeClient:    kubeClient,\n\t}\n}\n\nfunc (c *ClusterMigrator) Run(ctx context.Context) error {\n\tcrdList := &apiextensionsv1.CustomResourceDefinitionList{}\n\n\tif err := c.kubeClient.List(ctx, crdList, c.labelSelector); err != nil {\n\t\treturn fmt.Errorf(\"failed to list CRDs: %w\", err)\n\t}\n\n\tfor _, crd := range crdList.Items {\n\t\tif err := c.migrateCRD(ctx, crd.Name); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *ClusterMigrator) migrateCRD(ctx context.Context, name string) error {\n\tcrd := &apiextensionsv1.CustomResourceDefinition{}\n\n\tif err := c.kubeClient.Get(ctx, client.ObjectKey{Name: name}, crd); err != nil {\n\t\treturn fmt.Errorf(\"failed to get CRD %s: %w\", name, err)\n\t}\n\n\t// get the latest storage version for the CRD\n\tstorageVersion := c.getStorageVersion(crd)\n\tif storageVersion == \"\" {\n\t\treturn fmt.Errorf(\"no storage version found for CRD %s\", name)\n\t}\n\n\t// migrate all the resources for the CRD\n\terr := retry.RetryOnConflict(retry.DefaultRetry, func() error {\n\t\treturn c.migrateCR(ctx, crd, storageVersion)\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to migrate resources for CRD %s: %w\", name, err)\n\t}\n\n\t// set the CRD status to contain only the latest storage version\n\tif len(crd.Status.StoredVersions) > 1 || crd.Status.StoredVersions[0] != storageVersion {\n\t\tcrd.Status.StoredVersions = []string{storageVersion}\n\t\tif err := c.kubeClient.Status().Update(ctx, crd); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to update CRD %s status: %w\", crd.Name, err)\n\t\t}\n\t\tlogger.Successf(\"%s migrated to storage version %s\", crd.Name, storageVersion)\n\t}\n\treturn nil\n}\n\n// migrateCR migrates all CRs for the given CRD to the specified version by patching them.\nfunc (c *ClusterMigrator) migrateCR(ctx context.Context, crd *apiextensionsv1.CustomResourceDefinition, version string) error {\n\tlist := &unstructured.UnstructuredList{}\n\n\tapiVersion := crd.Spec.Group + \"/\" + version\n\tlistKind := crd.Spec.Names.ListKind\n\n\tlist.SetAPIVersion(apiVersion)\n\tlist.SetKind(listKind)\n\n\terr := c.kubeClient.List(ctx, list, client.InNamespace(\"\"))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to list resources for CRD %s: %w\", crd.Name, err)\n\t}\n\n\tif len(list.Items) == 0 {\n\t\treturn nil\n\t}\n\n\tfor _, item := range list.Items {\n\t\tpatches, err := ssa.PatchMigrateToVersion(&item, apiVersion)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create migration patch for %s/%s/%s: %w\",\n\t\t\t\titem.GetKind(), item.GetNamespace(), item.GetName(), err)\n\t\t}\n\n\t\tif len(patches) == 0 {\n\t\t\t// patch the resource with an empty patch to update the version\n\t\t\tif err := c.kubeClient.Patch(\n\t\t\t\tctx,\n\t\t\t\t&item,\n\t\t\t\tclient.RawPatch(client.Merge.Type(), []byte(\"{}\")),\n\t\t\t); err != nil && !apierrors.IsNotFound(err) {\n\t\t\t\treturn fmt.Errorf(\" %s/%s/%s failed to migrate: %w\",\n\t\t\t\t\titem.GetKind(), item.GetNamespace(), item.GetName(), err)\n\t\t\t}\n\t\t} else {\n\t\t\t// patch the resource to migrate the managed fields to the latest apiVersion\n\t\t\trawPatch, err := json.Marshal(patches)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to marshal migration patch for %s/%s/%s: %w\",\n\t\t\t\t\titem.GetKind(), item.GetNamespace(), item.GetName(), err)\n\t\t\t}\n\t\t\tif err := c.kubeClient.Patch(\n\t\t\t\tctx,\n\t\t\t\t&item,\n\t\t\t\tclient.RawPatch(types.JSONPatchType, rawPatch),\n\t\t\t); err != nil && !apierrors.IsNotFound(err) {\n\t\t\t\treturn fmt.Errorf(\" %s/%s/%s failed to migrate managed fields: %w\",\n\t\t\t\t\titem.GetKind(), item.GetNamespace(), item.GetName(), err)\n\t\t\t}\n\t\t}\n\n\t\tlogger.Successf(\"%s/%s/%s migrated to version %s\",\n\t\t\titem.GetKind(), item.GetNamespace(), item.GetName(), version)\n\t}\n\n\treturn nil\n}\n\n// getStorageVersion retrieves the storage version of a CustomResourceDefinition.\nfunc (c *ClusterMigrator) getStorageVersion(crd *apiextensionsv1.CustomResourceDefinition) string {\n\tvar version string\n\tfor _, v := range crd.Spec.Versions {\n\t\tif v.Storage {\n\t\t\tversion = v.Name\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn version\n}\n\n// WritableFS extends fs.FS with a WriteFile method.\ntype WritableFS interface {\n\tfs.FS\n\tWriteFile(name string, data []byte, perm os.FileMode) error\n}\n\n// osFS is a WritableFS implementation that uses the file system of the OS.\ntype osFS struct {\n\tfs.FS\n}\n\nfunc (o *osFS) WriteFile(name string, data []byte, perm os.FileMode) error {\n\treturn os.WriteFile(name, data, perm)\n}\n\n// FileSystemMigrator migrates all the CRs found in the manifests located in the specified path.\ntype FileSystemMigrator struct {\n\tfileSystem     WritableFS\n\tyes            bool\n\tdryRun         bool\n\tpath           string\n\textensions     []string\n\tlatestVersions map[schema.GroupKind]string\n}\n\n// FileAPIUpgrades represents the API upgrades detected in a specific manifest file.\ntype FileAPIUpgrades struct {\n\tFile     string\n\tUpgrades []APIUpgrade\n}\n\n// APIUpgrade represents an upgrade of a specific API version in a manifest file.\ntype APIUpgrade struct {\n\tLine       int\n\tKind       string\n\tOldVersion string\n\tNewVersion string\n}\n\n// NewFileSystemMigrator creates a new FileSystemMigrator instance with the specified flags.\nfunc NewFileSystemMigrator(fileSystem WritableFS, yes, dryRun bool, path string,\n\textensions []string, latestVersions map[schema.GroupKind]string) *FileSystemMigrator {\n\treturn &FileSystemMigrator{\n\t\tfileSystem:     fileSystem,\n\t\tyes:            yes,\n\t\tdryRun:         dryRun,\n\t\tpath:           filepath.Clean(path), // convert dir/ to dir to avoid error when walking\n\t\textensions:     extensions,\n\t\tlatestVersions: latestVersions,\n\t}\n}\n\nfunc (f *FileSystemMigrator) Run() error {\n\tlogger.Actionf(\"starting migration of custom resources\")\n\n\t// List and filter files.\n\tfiles, err := f.listFiles()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Detect upgrades.\n\tupgrades, err := f.detectUpgrades(files)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(upgrades) == 0 {\n\t\tlogger.Successf(\"no custom resources found that require migration\")\n\t\treturn nil\n\t}\n\tif f.dryRun {\n\t\tlogger.Successf(\"dry-run mode enabled, no changes will be made\")\n\t\treturn nil\n\t}\n\n\t// Confirm upgrades.\n\tif !f.yes {\n\t\tprompt := promptui.Prompt{\n\t\t\tLabel:     \"Are you sure you want to proceed with the above upgrades\", // Already prints \"? [y/N]\"\n\t\t\tIsConfirm: true,\n\t\t}\n\t\tif _, err := prompt.Run(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Migrate files.\n\tfor _, fileUpgrades := range upgrades {\n\t\tif err := f.migrateFile(&fileUpgrades); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlogger.Successf(\"file %s migrated successfully\", fileUpgrades.File)\n\t}\n\n\tlogger.Successf(\"custom resources migrated successfully\")\n\treturn nil\n}\n\nfunc (f *FileSystemMigrator) listFiles() ([]string, error) {\n\tfileInfo, err := fs.Stat(f.fileSystem, f.path)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to stat path %s: %w\", f.path, err)\n\t}\n\tif fileInfo.IsDir() {\n\t\treturn f.listDirectoryFiles()\n\t}\n\tif err := f.validateSingleFile(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn []string{f.path}, nil\n}\n\nfunc (f *FileSystemMigrator) listDirectoryFiles() ([]string, error) {\n\tvar files []string\n\terr := fs.WalkDir(f.fileSystem, f.path, func(path string, dirEntry fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !f.matchesExtensions(path) {\n\t\t\treturn nil\n\t\t}\n\t\tfileInfo, err := dirEntry.Info()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fileInfo.Mode().IsRegular() {\n\t\t\tfiles = append(files, path)\n\t\t} else if !fileInfo.IsDir() {\n\t\t\tlogger.Warningf(\"skipping irregular file %s\", path)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to walk directory %s: %w\", f.path, err)\n\t}\n\treturn files, nil\n}\n\nfunc (f *FileSystemMigrator) validateSingleFile() error {\n\tif !f.matchesExtensions(f.path) {\n\t\treturn fmt.Errorf(\"file %s does not match the specified extensions: %v\",\n\t\t\tf.path, strings.Join(f.extensions, \", \"))\n\t}\n\n\t// Check if it's irregular by walking the parent directory.\n\tvar irregular bool\n\terr := fs.WalkDir(f.fileSystem, filepath.Dir(f.path), func(path string, dirEntry fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif path != f.path {\n\t\t\treturn nil\n\t\t}\n\t\tfileInfo, err := dirEntry.Info()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !fileInfo.Mode().IsRegular() {\n\t\t\tirregular = true\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to validate file %s: %w\", f.path, err)\n\t}\n\tif irregular {\n\t\treturn fmt.Errorf(\"file %s is irregular\", f.path)\n\t}\n\treturn nil\n}\n\nfunc (f *FileSystemMigrator) matchesExtensions(file string) bool {\n\tfor _, ext := range f.extensions {\n\t\tif strings.HasSuffix(file, ext) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (f *FileSystemMigrator) detectUpgrades(files []string) ([]FileAPIUpgrades, error) {\n\tvar upgrades []FileAPIUpgrades\n\tfor _, file := range files {\n\t\tfileUpgrades, err := f.detectFileUpgrades(file)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif len(fileUpgrades) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tfu := FileAPIUpgrades{\n\t\t\tFile:     file,\n\t\t\tUpgrades: fileUpgrades,\n\t\t}\n\t\tupgrades = append(upgrades, fu)\n\t\tf.printDetectedUpgrades(&fu)\n\t}\n\treturn upgrades, nil\n}\n\nfunc (f *FileSystemMigrator) detectFileUpgrades(file string) ([]APIUpgrade, error) {\n\tb, err := fs.ReadFile(f.fileSystem, file)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read file %s: %w\", file, err)\n\t}\n\tlines := strings.Split(string(b), \"\\n\")\n\n\tvar fileUpgrades []APIUpgrade\n\tfor line, apiVersionLine := range lines {\n\t\t// Parse apiVersion.\n\t\tconst apiVersionPrefix = \"apiVersion: \"\n\t\tidx := strings.Index(apiVersionLine, apiVersionPrefix)\n\t\tif idx == -1 {\n\t\t\tcontinue\n\t\t}\n\t\tapiVersionValuePrefix := strings.TrimSpace(apiVersionLine[idx+len(apiVersionPrefix):])\n\t\tapiVersion := strings.Split(apiVersionValuePrefix, \" \")[0]\n\t\tgv, err := schema.ParseGroupVersion(apiVersion)\n\t\tif err != nil {\n\t\t\tlogger.Warningf(\"%s:%d: %v\", file, line+1, err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Parse kind.\n\t\tif line+1 >= len(lines) {\n\t\t\tcontinue\n\t\t}\n\t\tkindLine := lines[line+1]\n\t\tconst kindPrefix = \"kind: \"\n\t\tidx = strings.Index(kindLine, kindPrefix)\n\t\tif idx == -1 {\n\t\t\tcontinue\n\t\t}\n\t\tkindValuePrefix := strings.TrimSpace(kindLine[idx+len(kindPrefix):])\n\t\tkind := strings.Split(kindValuePrefix, \" \")[0]\n\n\t\t// Build GroupKind.\n\t\tgk := schema.GroupKind{\n\t\t\tGroup: gv.Group,\n\t\t\tKind:  kind,\n\t\t}\n\n\t\t// Check if there's a newer version for the GroupKind.\n\t\tlatestVersion, ok := f.latestVersions[gk]\n\t\tif !ok || latestVersion == gv.Version {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Record the upgrade.\n\t\tfileUpgrades = append(fileUpgrades, APIUpgrade{\n\t\t\tLine:       line,\n\t\t\tKind:       kind,\n\t\t\tOldVersion: gv.Version,\n\t\t\tNewVersion: latestVersion,\n\t\t})\n\t}\n\treturn fileUpgrades, nil\n}\n\nfunc (f *FileSystemMigrator) printDetectedUpgrades(fileUpgrades *FileAPIUpgrades) {\n\tfor _, upgrade := range fileUpgrades.Upgrades {\n\t\tlogger.Generatef(\"%s:%d: %s %s -> %s\",\n\t\t\tfileUpgrades.File,\n\t\t\tupgrade.Line+1,\n\t\t\tupgrade.Kind,\n\t\t\tupgrade.OldVersion,\n\t\t\tupgrade.NewVersion)\n\t}\n}\n\nfunc (f *FileSystemMigrator) migrateFile(fileUpgrades *FileAPIUpgrades) error {\n\t// Read file and map lines.\n\tb, err := fs.ReadFile(f.fileSystem, fileUpgrades.File)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %s: %w\", fileUpgrades.File, err)\n\t}\n\tlines := strings.Split(string(b), \"\\n\")\n\n\t// Apply upgrades to lines.\n\tfor _, upgrade := range fileUpgrades.Upgrades {\n\t\tline := lines[upgrade.Line]\n\t\tline = strings.Replace(line, upgrade.OldVersion, upgrade.NewVersion, 1)\n\t\tlines[upgrade.Line] = line\n\t}\n\n\t// Read file info to preserve permissions.\n\tfileInfo, err := fs.Stat(f.fileSystem, fileUpgrades.File)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to stat file %s: %w\", fileUpgrades.File, err)\n\t}\n\n\t// Write file with preserved permissions.\n\tb = []byte(strings.Join(lines, \"\\n\"))\n\tif err := f.fileSystem.WriteFile(fileUpgrades.File, b, fileInfo.Mode()); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %s: %w\", fileUpgrades.File, err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/migrate_test.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"io/fs\"\n\t\"os\"\n\t\"testing\"\n\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype writeToMemoryFS struct {\n\tfs.FS\n\n\twrittenFiles map[string][]byte\n}\n\nfunc (m *writeToMemoryFS) WriteFile(name string, data []byte, perm os.FileMode) error {\n\tm.writtenFiles[name] = data\n\treturn nil\n}\n\ntype writtenFile struct {\n\tfile       string\n\tgoldenFile string\n}\n\nfunc TestFileSystemMigrator(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tname         string\n\t\tpath         string\n\t\toutputGolden string\n\t\twrittenFiles []writtenFile\n\t\terr          string\n\t}{\n\t\t{\n\t\t\tname: \"errors out for single file that is a symlink\",\n\t\t\tpath: \"testdata/migrate/file-system/single-file-link.yaml\",\n\t\t\terr:  \"file testdata/migrate/file-system/single-file-link.yaml is irregular\",\n\t\t},\n\t\t{\n\t\t\tname: \"errors out for single file with wrong extension\",\n\t\t\tpath: \"testdata/migrate/file-system/single-file-wrong-ext.json\",\n\t\t\terr:  \"file testdata/migrate/file-system/single-file-wrong-ext.json does not match the specified extensions: .yaml, .yml\",\n\t\t},\n\t\t{\n\t\t\tname:         \"migrate single file\",\n\t\t\tpath:         \"testdata/migrate/file-system/single-file.yaml\",\n\t\t\toutputGolden: \"testdata/migrate/file-system/single-file.yaml.output.golden\",\n\t\t\twrittenFiles: []writtenFile{\n\t\t\t\t{\n\t\t\t\t\tfile:       \"testdata/migrate/file-system/single-file.yaml\",\n\t\t\t\t\tgoldenFile: \"testdata/migrate/file-system/single-file.yaml.golden\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"migrate files in directory\",\n\t\t\tpath:         \"testdata/migrate/file-system/dir\",\n\t\t\toutputGolden: \"testdata/migrate/file-system/dir.output.golden\",\n\t\t\twrittenFiles: []writtenFile{\n\t\t\t\t{\n\t\t\t\t\tfile:       \"testdata/migrate/file-system/dir/some-dir/another-file.yaml\",\n\t\t\t\t\tgoldenFile: \"testdata/migrate/file-system/dir.golden/some-dir/another-file.yaml\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tfile:       \"testdata/migrate/file-system/dir/some-dir/another-file.yml\",\n\t\t\t\t\tgoldenFile: \"testdata/migrate/file-system/dir.golden/some-dir/another-file.yml\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tfile:       \"testdata/migrate/file-system/dir/some-file.yaml\",\n\t\t\t\t\tgoldenFile: \"testdata/migrate/file-system/dir.golden/some-file.yaml\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tfile:       \"testdata/migrate/file-system/dir/some-file.yml\",\n\t\t\t\t\tgoldenFile: \"testdata/migrate/file-system/dir.golden/some-file.yml\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tg := NewWithT(t)\n\n\t\t\t// Store logger, replace with test logger, and restore at the end of the test.\n\t\t\tvar testLogger bytes.Buffer\n\t\t\toldLogger := logger\n\t\t\tlogger = stderrLogger{&testLogger}\n\t\t\tt.Cleanup(func() { logger = oldLogger })\n\n\t\t\t// Open current working directory as root and build write-to-memory filesystem.\n\t\t\tpathRoot, err := os.OpenRoot(\".\")\n\t\t\tg.Expect(err).ToNot(HaveOccurred())\n\t\t\tt.Cleanup(func() { pathRoot.Close() })\n\t\t\tfileSystem := &writeToMemoryFS{\n\t\t\t\tFS:           pathRoot.FS(),\n\t\t\t\twrittenFiles: make(map[string][]byte),\n\t\t\t}\n\n\t\t\t// Prepare other inputs.\n\t\t\tconst yes = true\n\t\t\tconst dryRun = false\n\t\t\textensions := []string{\".yaml\", \".yml\"}\n\t\t\tlatestVersions := map[schema.GroupKind]string{\n\t\t\t\t{Group: \"image.toolkit.fluxcd.io\", Kind: \"ImageRepository\"}:       \"v1\",\n\t\t\t\t{Group: \"image.toolkit.fluxcd.io\", Kind: \"ImagePolicy\"}:           \"v1\",\n\t\t\t\t{Group: \"image.toolkit.fluxcd.io\", Kind: \"ImageUpdateAutomation\"}: \"v1\",\n\t\t\t}\n\n\t\t\t// Run migration.\n\t\t\terr = NewFileSystemMigrator(fileSystem, yes, dryRun, tt.path, extensions, latestVersions).Run()\n\t\t\tif tt.err != \"\" {\n\t\t\t\tg.Expect(err).To(HaveOccurred())\n\t\t\t\tg.Expect(err.Error()).To(Equal(tt.err))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tg.Expect(err).ToNot(HaveOccurred())\n\n\t\t\t// Assert logger output.\n\t\t\tb, err := os.ReadFile(tt.outputGolden)\n\t\t\tg.Expect(err).ToNot(HaveOccurred())\n\t\t\tg.Expect(string(b)).To(Equal(testLogger.String()),\n\t\t\t\t\"logger output does not match golden file %s\", tt.outputGolden)\n\n\t\t\t// Assert which files were written.\n\t\t\twrittenFiles := make([]string, 0, len(fileSystem.writtenFiles))\n\t\t\tfor name := range fileSystem.writtenFiles {\n\t\t\t\twrittenFiles = append(writtenFiles, name)\n\t\t\t}\n\t\t\texpectedWrittenFiles := make([]string, 0, len(tt.writtenFiles))\n\t\t\tfor _, wf := range tt.writtenFiles {\n\t\t\t\texpectedWrittenFiles = append(expectedWrittenFiles, wf.file)\n\t\t\t}\n\t\t\tg.Expect(writtenFiles).To(ConsistOf(expectedWrittenFiles))\n\n\t\t\t// Assert contents of written files.\n\t\t\tfor _, wf := range tt.writtenFiles {\n\t\t\t\tb, err := os.ReadFile(wf.goldenFile)\n\t\t\t\tg.Expect(err).ToNot(HaveOccurred())\n\t\t\t\tg.Expect(string(fileSystem.writtenFiles[wf.file])).To(Equal(string(b)),\n\t\t\t\t\t\"file %s does not match golden file %s\", wf.file, wf.goldenFile)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/object.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n)\n\n// Most commands need one or both of the kind (e.g.,\n// `\"ImageRepository\"`) and a human-palatable name for the kind (e.g.,\n// `\"image repository\"`), to be interpolated into output. It's\n// convenient to package these up ahead of time, then the command\n// implementation can pick whichever it wants to use.\ntype apiType struct {\n\tkind, humanKind string\n\tgroupVersion    schema.GroupVersion\n}\n\n// adapter is an interface for a wrapper or alias from which we can\n// get a controller-runtime deserialisable value. This is used so that\n// you can wrap an API type to give it other useful methods, but still\n// use values of the wrapper with `client.Client`, which only deals\n// with types that have been added to the schema.\ntype adapter interface {\n\tasClientObject() client.Object\n}\n\n// copyable is an interface for a wrapper or alias from which we can\n// get a deep copied client.Object, required when you e.g. want to\n// calculate a patch.\ntype copyable interface {\n\tdeepCopyClientObject() client.Object\n}\n\n// listAdapter is the analogue to adapter, but for lists; the\n// controller runtime distinguishes between methods dealing with\n// objects and lists.\ntype listAdapter interface {\n\tasClientList() client.ObjectList\n\tlen() int\n}\n\n// universalAdapter is an adapter for any client.Object. Use this if\n// there are no other methods needed.\ntype universalAdapter struct {\n\tobj client.Object\n}\n\nfunc (c universalAdapter) asClientObject() client.Object {\n\treturn c.obj\n}\n\n// named is for adapters that have Name and Namespace fields, which\n// are sometimes handy to get hold of. ObjectMeta implements these, so\n// they shouldn't need any extra work.\ntype named interface {\n\tGetName() string\n\tGetNamespace() string\n\tGetObjectKind() schema.ObjectKind\n\tSetName(string)\n\tSetNamespace(string)\n}\n\nfunc copyName(target, source named) {\n\ttarget.SetName(source.GetName())\n\ttarget.SetNamespace(source.GetNamespace())\n}\n"
  },
  {
    "path": "cmd/flux/oci.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/google/go-containerregistry/pkg/authn\"\n\t\"github.com/google/go-containerregistry/pkg/crane\"\n\n\t\"github.com/fluxcd/pkg/auth\"\n\t\"github.com/fluxcd/pkg/auth/azure\"\n\tauthutils \"github.com/fluxcd/pkg/auth/utils\"\n)\n\n// loginWithProvider gets a crane authentication option for the given provider and URL.\nfunc loginWithProvider(ctx context.Context, url, provider string) (crane.Option, authn.Authenticator, error) {\n\tvar opts []auth.Option\n\tif provider == azure.ProviderName {\n\t\topts = append(opts, auth.WithAllowShellOut())\n\t}\n\tauthenticator, err := authutils.GetArtifactRegistryCredentials(ctx, provider, url, opts...)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"could not login to provider %s with url %s: %w\", provider, url, err)\n\t}\n\treturn crane.WithAuth(authenticator), authenticator, nil\n}\n"
  },
  {
    "path": "cmd/flux/pull.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar pullCmd = &cobra.Command{\n\tUse:   \"pull\",\n\tShort: \"Pull artifacts\",\n\tLong:  `The pull command is used to download OCI artifacts.`,\n}\n\nfunc init() {\n\trootCmd.AddCommand(pullCmd)\n}\n"
  },
  {
    "path": "cmd/flux/pull_artifact.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/google/go-containerregistry/pkg/crane\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/fluxcd/pkg/oci\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n)\n\nvar pullArtifactCmd = &cobra.Command{\n\tUse:   \"artifact\",\n\tShort: \"Pull artifact\",\n\tLong: `The pull artifact command downloads and extracts the OCI artifact content to the given path.\nThe command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`,\n\tExample: `  # Pull an OCI artifact created by flux from GHCR\n  flux pull artifact oci://ghcr.io/org/manifests/app:v0.0.1 --output ./path/to/local/manifests\n`,\n\tRunE: pullArtifactCmdRun,\n}\n\ntype pullArtifactFlags struct {\n\toutput   string\n\tcreds    string\n\tinsecure bool\n\tprovider flags.SourceOCIProvider\n}\n\nvar pullArtifactArgs = newPullArtifactFlags()\n\nfunc newPullArtifactFlags() pullArtifactFlags {\n\treturn pullArtifactFlags{\n\t\tprovider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),\n\t}\n}\n\nfunc init() {\n\tpullArtifactCmd.Flags().StringVarP(&pullArtifactArgs.output, \"output\", \"o\", \"\", \"path where the artifact content should be extracted.\")\n\tpullArtifactCmd.Flags().StringVar(&pullArtifactArgs.creds, \"creds\", \"\", \"credentials for OCI registry in the format <username>[:<password>] if --provider is generic\")\n\tpullArtifactCmd.Flags().Var(&pullArtifactArgs.provider, \"provider\", sourceOCIRepositoryArgs.provider.Description())\n\tpullArtifactCmd.Flags().BoolVar(&pullArtifactArgs.insecure, \"insecure-registry\", false, \"allows artifacts to be pulled without TLS\")\n\tpullCmd.AddCommand(pullArtifactCmd)\n}\n\nfunc pullArtifactCmdRun(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"artifact URL is required\")\n\t}\n\tociURL := args[0]\n\n\tif pullArtifactArgs.output == \"\" {\n\t\treturn fmt.Errorf(\"output path cannot be empty\")\n\t}\n\n\tif fs, err := os.Stat(pullArtifactArgs.output); err != nil || !fs.IsDir() {\n\t\treturn fmt.Errorf(\"invalid output path %q: %w\", pullArtifactArgs.output, err)\n\t}\n\n\turl, err := oci.ParseArtifactURL(ociURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\topts := oci.DefaultOptions()\n\n\tif pullArtifactArgs.insecure {\n\t\topts = append(opts, crane.Insecure)\n\t}\n\n\tif pullArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {\n\t\tlogger.Actionf(\"logging in to registry with provider credentials\")\n\t\topt, _, err := loginWithProvider(ctx, url, pullArtifactArgs.provider.String())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error during login with provider: %w\", err)\n\t\t}\n\t\topts = append(opts, opt)\n\t}\n\n\tociClient := oci.NewClient(opts)\n\n\tif pullArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pullArtifactArgs.creds != \"\" {\n\t\tlogger.Actionf(\"logging in to registry with credentials\")\n\t\tif err := ociClient.LoginWithCredentials(pullArtifactArgs.creds); err != nil {\n\t\t\treturn fmt.Errorf(\"could not login with credentials: %w\", err)\n\t\t}\n\t}\n\n\tlogger.Actionf(\"pulling artifact from %s\", url)\n\n\tmeta, err := ociClient.Pull(ctx, url, pullArtifactArgs.output)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif meta.Source != \"\" {\n\t\tlogger.Successf(\"source %s\", meta.Source)\n\t}\n\tif meta.Revision != \"\" {\n\t\tlogger.Successf(\"revision %s\", meta.Revision)\n\t}\n\tlogger.Successf(\"digest %s\", meta.Digest)\n\tlogger.Successf(\"artifact content extracted to %s\", pullArtifactArgs.output)\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/push.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar pushCmd = &cobra.Command{\n\tUse:   \"push\",\n\tShort: \"Push artifacts\",\n\tLong:  `The push command is used to publish OCI artifacts.`,\n}\n\nfunc init() {\n\trootCmd.AddCommand(pushCmd)\n}\n"
  },
  {
    "path": "cmd/flux/push_artifact.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/go-containerregistry/pkg/authn\"\n\t\"github.com/google/go-containerregistry/pkg/crane\"\n\t\"github.com/google/go-containerregistry/pkg/logs\"\n\t\"github.com/google/go-containerregistry/pkg/name\"\n\t\"github.com/google/go-containerregistry/pkg/v1/remote\"\n\t\"github.com/google/go-containerregistry/pkg/v1/remote/transport\"\n\t\"github.com/spf13/cobra\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/pkg/oci\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n)\n\nvar pushArtifactCmd = &cobra.Command{\n\tUse:   \"artifact\",\n\tShort: \"Push artifact\",\n\tLong: `The push artifact command creates a tarball from the given directory or the single file and uploads the artifact to an OCI repository.\nThe command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`,\n\tExample: `  # Push manifests to GHCR using the short Git SHA as the OCI artifact tag\n  echo $GITHUB_PAT | docker login ghcr.io --username flux --password-stdin\n  flux push artifact oci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) \\\n\t--path=\"./path/to/local/manifests\" \\\n\t--source=\"$(git config --get remote.origin.url)\" \\\n\t--revision=\"$(git branch --show-current)@sha1:$(git rev-parse HEAD)\"\n\n  # Push and sign artifact with cosign\n  digest_url = $(flux push artifact \\\n\toci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) \\\n\t--source=\"$(git config --get remote.origin.url)\" \\\n\t--revision=\"$(git branch --show-current)@sha1:$(git rev-parse HEAD)\" \\\n\t--path=\"./path/to/local/manifest.yaml\" \\\n\t--output json | \\\n\tjq -r '. | .repository + \"@\" + .digest')\n  cosign sign $digest_url\n\n  # Push manifests passed into stdin to GHCR and set custom OCI annotations\n  kustomize build . | flux push artifact oci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) -f - \\ \n    --source=\"$(git config --get remote.origin.url)\" \\\n    --revision=\"$(git branch --show-current)@sha1:$(git rev-parse HEAD)\" \\\n    --annotations='org.opencontainers.image.licenses=Apache-2.0' \\\n    --annotations='org.opencontainers.image.documentation=https://app.org/docs' \\\n    --annotations='org.opencontainers.image.description=Production config.'\n\n  # Push single manifest file to GHCR using the short Git SHA as the OCI artifact tag\n  echo $GITHUB_PAT | docker login ghcr.io --username flux --password-stdin\n  flux push artifact oci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) \\\n\t--path=\"./path/to/local/manifest.yaml\" \\\n\t--source=\"$(git config --get remote.origin.url)\" \\\n\t--revision=\"$(git branch --show-current)@sha1:$(git rev-parse HEAD)\"\n\n  # Push manifests to Docker Hub using the Git tag as the OCI artifact tag\n  echo $DOCKER_PAT | docker login --username flux --password-stdin\n  flux push artifact oci://docker.io/org/app-config:$(git tag --points-at HEAD) \\\n\t--path=\"./path/to/local/manifests\" \\\n\t--source=\"$(git config --get remote.origin.url)\" \\\n\t--revision=\"$(git tag --points-at HEAD)@sha1:$(git rev-parse HEAD)\"\n\n  # Login directly to the registry provider\n  # You might need to export the following variable if you use local config files for AWS:\n  # export AWS_SDK_LOAD_CONFIG=1\n  flux push artifact oci://<account>.dkr.ecr.<region>.amazonaws.com/app-config:$(git tag --points-at HEAD) \\\n\t--path=\"./path/to/local/manifests\" \\\n\t--source=\"$(git config --get remote.origin.url)\" \\\n\t--revision=\"$(git tag --points-at HEAD)@sha1:$(git rev-parse HEAD)\" \\\n\t--provider aws\n\n  # Login by passing credentials directly\n  flux push artifact oci://docker.io/org/app-config:$(git tag --points-at HEAD) \\\n\t--path=\"./path/to/local/manifests\" \\\n\t--source=\"$(git config --get remote.origin.url)\" \\\n\t--revision=\"$(git tag --points-at HEAD)@sha1:$(git rev-parse HEAD)\" \\\n\t--creds flux:$DOCKER_PAT\n`,\n\tRunE: pushArtifactCmdRun,\n}\n\ntype pushArtifactFlags struct {\n\tpath         string\n\tsource       string\n\trevision     string\n\tcreds        string\n\tprovider     flags.SourceOCIProvider\n\tignorePaths  []string\n\tannotations  []string\n\toutput       string\n\tdebug        bool\n\treproducible bool\n\tinsecure     bool\n}\n\nvar pushArtifactArgs = newPushArtifactFlags()\n\nfunc newPushArtifactFlags() pushArtifactFlags {\n\treturn pushArtifactFlags{\n\t\tprovider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),\n\t}\n}\n\nfunc init() {\n\tpushArtifactCmd.Flags().StringVarP(&pushArtifactArgs.path, \"path\", \"f\", \"\", \"path to the directory where the Kubernetes manifests are located\")\n\tpushArtifactCmd.Flags().StringVar(&pushArtifactArgs.source, \"source\", \"\", \"the source address, e.g. the Git URL\")\n\tpushArtifactCmd.Flags().StringVar(&pushArtifactArgs.revision, \"revision\", \"\", \"the source revision in the format '<branch|tag>@sha1:<commit-sha>'\")\n\tpushArtifactCmd.Flags().StringVar(&pushArtifactArgs.creds, \"creds\", \"\", \"credentials for OCI registry in the format <username>[:<password>] if --provider is generic\")\n\tpushArtifactCmd.Flags().Var(&pushArtifactArgs.provider, \"provider\", pushArtifactArgs.provider.Description())\n\tpushArtifactCmd.Flags().StringSliceVar(&pushArtifactArgs.ignorePaths, \"ignore-paths\", excludeOCI, \"set paths to ignore in .gitignore format\")\n\tpushArtifactCmd.Flags().StringArrayVarP(&pushArtifactArgs.annotations, \"annotations\", \"a\", nil, \"Set custom OCI annotations in the format '<key>=<value>'\")\n\tpushArtifactCmd.Flags().StringVarP(&pushArtifactArgs.output, \"output\", \"o\", \"\",\n\t\t\"the format in which the artifact digest should be printed, can be 'json' or 'yaml'\")\n\tpushArtifactCmd.Flags().BoolVarP(&pushArtifactArgs.debug, \"debug\", \"\", false, \"display logs from underlying library\")\n\tpushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.reproducible, \"reproducible\", false, \"ensure reproducible image digests by setting the created timestamp to '1970-01-01T00:00:00Z'\")\n\tpushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.insecure, \"insecure-registry\", false, \"allows artifacts to be pushed without TLS\")\n\n\tpushCmd.AddCommand(pushArtifactCmd)\n}\n\nfunc pushArtifactCmdRun(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"artifact URL is required\")\n\t}\n\tociURL := args[0]\n\n\tif pushArtifactArgs.source == \"\" {\n\t\treturn fmt.Errorf(\"--source is required\")\n\t}\n\n\tif pushArtifactArgs.revision == \"\" {\n\t\treturn fmt.Errorf(\"--revision is required\")\n\t}\n\n\tif pushArtifactArgs.path == \"\" {\n\t\treturn fmt.Errorf(\"invalid path %q\", pushArtifactArgs.path)\n\t}\n\n\turl, err := oci.ParseArtifactURL(ociURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tref, err := name.ParseReference(url)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpath := pushArtifactArgs.path\n\tif pushArtifactArgs.path == \"-\" {\n\t\tpath, err = saveReaderToFile(os.Stdin)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdefer os.Remove(path)\n\t}\n\n\tif _, err := os.Stat(path); err != nil {\n\t\treturn fmt.Errorf(\"invalid path '%s', must point to an existing directory or file: %w\", path, err)\n\t}\n\n\tannotations := map[string]string{}\n\tfor _, annotation := range pushArtifactArgs.annotations {\n\t\tkv := strings.Split(annotation, \"=\")\n\t\tif len(kv) != 2 {\n\t\t\treturn fmt.Errorf(\"invalid annotation %s, must be in the format key=value\", annotation)\n\t\t}\n\t\tannotations[kv[0]] = kv[1]\n\t}\n\n\tif pushArtifactArgs.debug {\n\t\t// direct logs from crane library to stderr\n\t\t// this can be useful to figure out things happening underneath e.g when the library is retrying a request\n\t\tlogs.Warn.SetOutput(os.Stderr)\n\t}\n\n\tmeta := oci.Metadata{\n\t\tSource:      pushArtifactArgs.source,\n\t\tRevision:    pushArtifactArgs.revision,\n\t\tAnnotations: annotations,\n\t}\n\n\tif pushArtifactArgs.reproducible {\n\t\tzeroTime := time.Unix(0, 0)\n\t\tmeta.Created = zeroTime.Format(time.RFC3339)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tvar authenticator authn.Authenticator\n\topts := oci.DefaultOptions()\n\tif pushArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pushArtifactArgs.creds != \"\" {\n\t\tlogger.Actionf(\"logging in to registry with credentials\")\n\t\tauthenticator, err = oci.GetAuthFromCredentials(pushArtifactArgs.creds)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not login with credentials: %w\", err)\n\t\t}\n\t\topts = append(opts, crane.WithAuth(authenticator))\n\t}\n\n\tif provider := pushArtifactArgs.provider.String(); provider != sourcev1.GenericOCIProvider {\n\t\tlogger.Actionf(\"logging in to registry with provider credentials\")\n\t\tvar opt crane.Option\n\t\topt, authenticator, err = loginWithProvider(ctx, url, provider)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error during login with provider: %w\", err)\n\t\t}\n\t\topts = append(opts, opt)\n\t}\n\n\tif rootArgs.timeout != 0 {\n\t\tbackoff := remote.Backoff{\n\t\t\tDuration: 1.0 * time.Second,\n\t\t\tFactor:   3,\n\t\t\tJitter:   0.1,\n\t\t\t// timeout happens when the cap is exceeded or number of steps is reached\n\t\t\t// 10 steps is big enough that most reasonable cap(under 30min) will be exceeded before\n\t\t\t// the number of steps are completed.\n\t\t\tSteps: 10,\n\t\t\tCap:   rootArgs.timeout,\n\t\t}\n\n\t\tif authenticator == nil {\n\t\t\tauthenticator, err = authn.DefaultKeychain.Resolve(ref.Context())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\ttransportOpts, err := oci.WithRetryTransport(ctx,\n\t\t\tref,\n\t\t\tauthenticator,\n\t\t\tbackoff,\n\t\t\t[]string{ref.Context().Scope(transport.PushScope)},\n\t\t\tpushArtifactArgs.insecure,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error setting up transport: %w\", err)\n\t\t}\n\t\topts = append(opts, transportOpts, oci.WithRetryBackOff(backoff))\n\t}\n\n\tif pushArtifactArgs.output == \"\" {\n\t\tlogger.Actionf(\"pushing artifact to %s\", url)\n\t}\n\n\tif pushArtifactArgs.insecure {\n\t\topts = append(opts, crane.Insecure)\n\t}\n\n\tociClient := oci.NewClient(opts)\n\tdigestURL, err := ociClient.Push(ctx, url, path,\n\t\toci.WithPushMetadata(meta),\n\t\toci.WithPushIgnorePaths(pushArtifactArgs.ignorePaths...),\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"pushing artifact failed: %w\", err)\n\t}\n\n\tdigest, err := name.NewDigest(digestURL)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"artifact digest parsing failed: %w\", err)\n\t}\n\n\ttag, err := name.NewTag(url)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"artifact tag parsing failed: %w\", err)\n\t}\n\n\tinfo := struct {\n\t\tURL        string `json:\"url\"`\n\t\tRepository string `json:\"repository\"`\n\t\tTag        string `json:\"tag\"`\n\t\tDigest     string `json:\"digest\"`\n\t}{\n\t\tURL:        fmt.Sprintf(\"oci://%s\", digestURL),\n\t\tRepository: digest.Repository.Name(),\n\t\tTag:        tag.TagStr(),\n\t\tDigest:     digest.DigestStr(),\n\t}\n\n\tswitch pushArtifactArgs.output {\n\tcase \"json\":\n\t\tmarshalled, err := json.MarshalIndent(&info, \"\", \"  \")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"artifact digest JSON conversion failed: %w\", err)\n\t\t}\n\t\tmarshalled = append(marshalled, \"\\n\"...)\n\t\trootCmd.Print(string(marshalled))\n\tcase \"yaml\":\n\t\tmarshalled, err := yaml.Marshal(&info)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"artifact digest YAML conversion failed: %w\", err)\n\t\t}\n\t\trootCmd.Print(string(marshalled))\n\tdefault:\n\t\tlogger.Successf(\"artifact successfully pushed to %s\", digestURL)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/readiness.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tkstatus \"github.com/fluxcd/cli-utils/pkg/kstatus/status\"\n\tapimeta \"k8s.io/apimachinery/pkg/api/meta\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\t\"github.com/fluxcd/pkg/runtime/object\"\n\t\"github.com/fluxcd/pkg/runtime/patch\"\n)\n\n// objectStatusType is the type of object in terms of status when computing the\n// readiness of an object. Readiness check method depends on the type of object.\n// For a dynamic object, Ready status condition is considered only for the\n// latest generation of the object. For a static object that don't have any\n// condition, the object generation is not considered.\ntype objectStatusType int\n\nconst (\n\tobjectStatusDynamic objectStatusType = iota\n\tobjectStatusStatic\n)\n\n// isObjectReady determines if an object is ready using the kstatus.Compute()\n// result. statusType helps differenciate between static and dynamic objects to\n// accurately check the object's readiness. A dynamic object may have some extra\n// considerations depending on the object.\nfunc isObjectReady(obj client.Object, statusType objectStatusType) (bool, error) {\n\tobservedGen, err := object.GetStatusObservedGeneration(obj)\n\tif err != nil && err != object.ErrObservedGenerationNotFound {\n\t\treturn false, err\n\t}\n\n\tif statusType == objectStatusDynamic {\n\t\t// Object not reconciled yet.\n\t\tif observedGen < 1 {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tcobj, ok := obj.(meta.ObjectWithConditions)\n\t\tif !ok {\n\t\t\treturn false, fmt.Errorf(\"unable to get conditions from object\")\n\t\t}\n\n\t\tif c := apimeta.FindStatusCondition(cobj.GetConditions(), meta.ReadyCondition); c != nil {\n\t\t\t// Ensure that the ready condition is for the latest generation of\n\t\t\t// the object.\n\t\t\t// NOTE: Some APIs like ImageUpdateAutomation and HelmRelease don't\n\t\t\t// support per condition observed generation yet. Per condition\n\t\t\t// observed generation for them are always zero.\n\t\t\t// There are two strategies used across different object kinds to\n\t\t\t// check the latest ready condition:\n\t\t\t//   - check that the ready condition's generation matches the\n\t\t\t//     object's generation.\n\t\t\t//   - check that the observed generation of the object in the\n\t\t\t//     status matches the object's generation.\n\t\t\t//\n\t\t\t// TODO: Once ImageUpdateAutomation and HelmRelease APIs have per\n\t\t\t// condition observed generation, remove the object's observed\n\t\t\t// generation and object's generation check (the second condition\n\t\t\t// below). Also, try replacing this readiness check function with\n\t\t\t// fluxcd/pkg/ssa's ResourceManager.Wait(), which uses kstatus\n\t\t\t// internally to check readiness of the objects.\n\t\t\tif c.ObservedGeneration != 0 && c.ObservedGeneration != obj.GetGeneration() {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t\tif c.ObservedGeneration == 0 && observedGen != obj.GetGeneration() {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t} else {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\tu, err := patch.ToUnstructured(obj)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tresult, err := kstatus.Compute(u)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tswitch result.Status {\n\tcase kstatus.CurrentStatus:\n\t\treturn true, nil\n\tcase kstatus.InProgressStatus:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"%s\", result.Message)\n\t}\n}\n\n// isObjectReadyConditionFunc returns a wait.ConditionFunc to be used with\n// wait.Poll* while polling for an object with dynamic status to be ready.\nfunc isObjectReadyConditionFunc(kubeClient client.Client, namespaceName types.NamespacedName, obj client.Object) wait.ConditionWithContextFunc {\n\treturn func(ctx context.Context) (bool, error) {\n\t\terr := kubeClient.Get(ctx, namespaceName, obj)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\treturn isObjectReady(obj, objectStatusDynamic)\n\t}\n}\n\n// isStaticObjectReadyConditionFunc returns a wait.ConditionFunc to be used with\n// wait.Poll* while polling for an object with static or no status to be\n// ready.\nfunc isStaticObjectReadyConditionFunc(kubeClient client.Client, namespaceName types.NamespacedName, obj client.Object) wait.ConditionWithContextFunc {\n\treturn func(ctx context.Context) (bool, error) {\n\t\terr := kubeClient.Get(ctx, namespaceName, obj)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\treturn isObjectReady(obj, objectStatusStatic)\n\t}\n}\n\n// kstatusCompute returns the kstatus computed result of a given object.\nfunc kstatusCompute(obj client.Object) (result *kstatus.Result, err error) {\n\tu, err := patch.ToUnstructured(obj)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\treturn kstatus.Compute(u)\n}\n"
  },
  {
    "path": "cmd/flux/readiness_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\t\"github.com/fluxcd/pkg/runtime/conditions\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nfunc Test_isObjectReady(t *testing.T) {\n\t// Ready object.\n\treadyObj := &sourcev1.GitRepository{}\n\treadyObj.Generation = 1\n\treadyObj.Status.ObservedGeneration = 1\n\tconditions.MarkTrue(readyObj, meta.ReadyCondition, \"foo1\", \"bar1\")\n\n\t// Not ready object.\n\tnotReadyObj := readyObj.DeepCopy()\n\tconditions.MarkFalse(notReadyObj, meta.ReadyCondition, \"foo2\", \"bar2\")\n\n\t// Not reconciled object.\n\tnotReconciledObj := readyObj.DeepCopy()\n\tnotReconciledObj.Status = sourcev1.GitRepositoryStatus{ObservedGeneration: -1}\n\n\t// No condition.\n\tnoConditionObj := readyObj.DeepCopy()\n\tnoConditionObj.Status = sourcev1.GitRepositoryStatus{ObservedGeneration: 1}\n\n\t// Outdated condition.\n\treadyObjOutdated := readyObj.DeepCopy()\n\treadyObjOutdated.Generation = 2\n\n\t// Object without per condition observed generation.\n\toldObj := readyObj.DeepCopy()\n\treadyTrueCondn := conditions.TrueCondition(meta.ReadyCondition, \"foo3\", \"bar3\")\n\toldObj.Status.Conditions = []metav1.Condition{*readyTrueCondn}\n\n\t// Outdated object without per condition observed generation.\n\toldObjOutdated := oldObj.DeepCopy()\n\toldObjOutdated.Generation = 2\n\n\t// Empty status object.\n\tstaticObj := readyObj.DeepCopy()\n\tstaticObj.Status = sourcev1.GitRepositoryStatus{}\n\n\t// No status object.\n\tnoStatusObj := &notificationv1.Provider{}\n\tnoStatusObj.Generation = 1\n\n\ttype args struct {\n\t\tobj        client.Object\n\t\tstatusType objectStatusType\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    bool\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"dynamic ready\",\n\t\t\targs: args{obj: readyObj, statusType: objectStatusDynamic},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"dynamic not ready\",\n\t\t\targs: args{obj: notReadyObj, statusType: objectStatusDynamic},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"dynamic not reconciled\",\n\t\t\targs: args{obj: notReconciledObj, statusType: objectStatusDynamic},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"dynamic not condition\",\n\t\t\targs: args{obj: noConditionObj, statusType: objectStatusDynamic},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"dynamic ready outdated\",\n\t\t\targs: args{obj: readyObjOutdated, statusType: objectStatusDynamic},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"dynamic ready without per condition gen\",\n\t\t\targs: args{obj: oldObj, statusType: objectStatusDynamic},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"dynamic outdated ready status without per condition gen\",\n\t\t\targs: args{obj: oldObjOutdated, statusType: objectStatusDynamic},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"static empty status\",\n\t\t\targs: args{obj: staticObj, statusType: objectStatusStatic},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"static no status\",\n\t\t\targs: args{obj: noStatusObj, statusType: objectStatusStatic},\n\t\t\twant: 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 := isObjectReady(tt.args.obj, tt.args.statusType)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"isObjectReady() 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(\"isObjectReady() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/receiver.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n)\n\n// notificationv1.Receiver\n\nvar receiverType = apiType{\n\tkind:         notificationv1.ReceiverKind,\n\thumanKind:    \"receiver\",\n\tgroupVersion: notificationv1.GroupVersion,\n}\n\ntype receiverAdapter struct {\n\t*notificationv1.Receiver\n}\n\nfunc (a receiverAdapter) asClientObject() client.Object {\n\treturn a.Receiver\n}\n\nfunc (a receiverAdapter) deepCopyClientObject() client.Object {\n\treturn a.Receiver.DeepCopy()\n}\n\n// notificationv1.Receiver\n\ntype receiverListAdapter struct {\n\t*notificationv1.ReceiverList\n}\n\nfunc (a receiverListAdapter) asClientList() client.ObjectList {\n\treturn a.ReceiverList\n}\n\nfunc (a receiverListAdapter) len() int {\n\treturn len(a.ReceiverList.Items)\n}\n"
  },
  {
    "path": "cmd/flux/reconcile.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\tkstatus \"github.com/fluxcd/cli-utils/pkg/kstatus/status\"\n\t\"github.com/spf13/cobra\"\n\tapimeta \"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"k8s.io/client-go/util/retry\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar reconcileCmd = &cobra.Command{\n\tUse:   \"reconcile\",\n\tShort: \"Reconcile sources and resources\",\n\tLong:  `The reconcile sub-commands trigger a reconciliation of sources and resources.`,\n}\n\nfunc init() {\n\trootCmd.AddCommand(reconcileCmd)\n}\n\ntype reconcileCommand struct {\n\tapiType\n\tobject reconcilable\n}\n\ntype reconcilable interface {\n\tadapter     // to be able to load from the cluster\n\tcopyable    // to be able to calculate patches\n\tsuspendable // to tell if it's suspended\n\n\t// these are implemented by anything embedding metav1.ObjectMeta\n\tGetAnnotations() map[string]string\n\tSetAnnotations(map[string]string)\n\n\tisStatic() bool                      // is it a static object that does not have a reconciler?\n\tlastHandledReconcileRequest() string // what was the last handled reconcile request?\n\tsuccessMessage() string              // what do you want to tell people when successfully reconciled?\n}\n\nfunc reconcilableConditions(object reconcilable) []metav1.Condition {\n\tif s, ok := object.(meta.ObjectWithConditions); ok {\n\t\treturn s.GetConditions()\n\t}\n\n\tif s, ok := object.(oldConditions); ok {\n\t\treturn *s.GetStatusConditions()\n\t}\n\n\treturn []metav1.Condition{}\n}\n\nfunc (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"%s name is required\", reconcile.kind)\n\t}\n\tname := args[0]\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\tName:      name,\n\t}\n\n\terr = kubeClient.Get(ctx, namespacedName, reconcile.object.asClientObject())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif reconcile.object.isStatic() {\n\t\tlogger.Successf(\"reconciliation not supported by the object\")\n\t\treturn nil\n\t}\n\n\tif reconcile.object.isSuspended() {\n\t\treturn fmt.Errorf(\"resource is suspended\")\n\t}\n\n\tlogger.Actionf(\"annotating %s %s in %s namespace\", reconcile.kind, name, *kubeconfigArgs.Namespace)\n\tif err := requestReconciliation(ctx, kubeClient, namespacedName,\n\t\treconcile.groupVersion.WithKind(reconcile.kind)); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"%s annotated\", reconcile.kind)\n\n\tlastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()\n\tlogger.Waitingf(\"waiting for %s reconciliation\", reconcile.kind)\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,\n\t\treconciliationHandled(kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {\n\t\treturn err\n\t}\n\treadyCond := apimeta.FindStatusCondition(reconcilableConditions(reconcile.object), meta.ReadyCondition)\n\tif readyCond == nil {\n\t\treturn fmt.Errorf(\"status can't be determined\")\n\t}\n\n\tif readyCond.Status != metav1.ConditionTrue {\n\t\treturn fmt.Errorf(\"%s reconciliation failed: '%s'\", reconcile.kind, readyCond.Message)\n\t}\n\tlogger.Successf(\"%s\", reconcile.object.successMessage())\n\treturn nil\n}\n\nfunc reconciliationHandled(kubeClient client.Client, namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionWithContextFunc {\n\treturn func(ctx context.Context) (bool, error) {\n\t\terr := kubeClient.Get(ctx, namespacedName, obj.asClientObject())\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif obj.lastHandledReconcileRequest() == lastHandledReconcileAt {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tresult, err := kstatusCompute(obj.asClientObject())\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tswitch result.Status {\n\t\tcase kstatus.CurrentStatus:\n\t\t\treturn true, nil\n\t\tcase kstatus.InProgressStatus:\n\t\t\treturn false, nil\n\t\tdefault:\n\t\t\treturn false, fmt.Errorf(\"%s\", result.Message)\n\t\t}\n\t}\n}\n\nfunc requestReconciliation(ctx context.Context, kubeClient client.Client,\n\tnamespacedName types.NamespacedName, gvk schema.GroupVersionKind) error {\n\treturn retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {\n\t\tobject := &metav1.PartialObjectMetadata{}\n\t\tobject.SetGroupVersionKind(gvk)\n\t\tobject.SetName(namespacedName.Name)\n\t\tobject.SetNamespace(namespacedName.Namespace)\n\t\tif err := kubeClient.Get(ctx, namespacedName, object); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpatch := client.MergeFrom(object.DeepCopy())\n\n\t\t// Add a timestamp annotation to trigger a reconciliation.\n\t\tts := time.Now().Format(time.RFC3339Nano)\n\t\tannotations := object.GetAnnotations()\n\t\tif annotations == nil {\n\t\t\tannotations = make(map[string]string, 1)\n\t\t}\n\t\tannotations[meta.ReconcileRequestAnnotation] = ts\n\n\t\t// HelmRelease specific annotations to force or reset a release.\n\t\tif gvk.Kind == helmv2.HelmReleaseKind {\n\t\t\tif rhrArgs.syncForce {\n\t\t\t\tannotations[helmv2.ForceRequestAnnotation] = ts\n\t\t\t}\n\t\t\tif rhrArgs.syncReset {\n\t\t\t\tannotations[helmv2.ResetRequestAnnotation] = ts\n\t\t\t}\n\t\t}\n\n\t\tobject.SetAnnotations(annotations)\n\t\treturn kubeClient.Patch(ctx, object, patch)\n\t})\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_helmrelease.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar reconcileHrCmd = &cobra.Command{\n\tUse:     \"helmrelease [name]\",\n\tAliases: []string{\"hr\"},\n\tShort:   \"Reconcile a HelmRelease resource\",\n\tLong: `\nThe reconcile helmrelease command triggers a reconciliation of a HelmRelease resource and waits for it to finish.`,\n\tExample: `  # Trigger a HelmRelease apply outside of the reconciliation interval\n  flux reconcile hr podinfo\n\n  # Trigger a reconciliation of the HelmRelease's source and apply changes\n  flux reconcile hr podinfo --with-source`,\n\tValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),\n\tRunE: reconcileWithSourceCommand{\n\t\tapiType: helmReleaseType,\n\t\tobject:  helmReleaseAdapter{&helmv2.HelmRelease{}},\n\t}.run,\n}\n\ntype reconcileHelmReleaseFlags struct {\n\tsyncHrWithSource bool\n\tsyncForce        bool\n\tsyncReset        bool\n}\n\nvar rhrArgs reconcileHelmReleaseFlags\n\nfunc init() {\n\treconcileHrCmd.Flags().BoolVar(&rhrArgs.syncHrWithSource, \"with-source\", false, \"reconcile HelmRelease source\")\n\treconcileHrCmd.Flags().BoolVar(&rhrArgs.syncForce, \"force\", false, \"force a one-off install or upgrade of the HelmRelease resource\")\n\treconcileHrCmd.Flags().BoolVar(&rhrArgs.syncReset, \"reset\", false, \"reset the failure count for this HelmRelease resource\")\n\treconcileCmd.AddCommand(reconcileHrCmd)\n}\n\nfunc (obj helmReleaseAdapter) lastHandledReconcileRequest() string {\n\treturn obj.Status.GetLastHandledReconcileRequest()\n}\n\nfunc (obj helmReleaseAdapter) reconcileSource() bool {\n\treturn rhrArgs.syncHrWithSource\n}\n\nfunc (obj helmReleaseAdapter) getSource() (reconcileSource, sourceReference) {\n\tvar (\n\t\tname string\n\t\tns   string\n\t)\n\tswitch {\n\tcase obj.Spec.ChartRef != nil:\n\t\tname, ns = obj.Spec.ChartRef.Name, obj.Spec.ChartRef.Namespace\n\t\tif ns == \"\" {\n\t\t\tns = obj.Namespace\n\t\t}\n\t\tsrcRef := sourceReference{\n\t\t\tkind:      obj.Spec.ChartRef.Kind,\n\t\t\tname:      name,\n\t\t\tnamespace: ns,\n\t\t}\n\t\tswitch obj.Spec.ChartRef.Kind {\n\t\tcase sourcev1.HelmChartKind:\n\t\t\treturn reconcileWithSourceCommand{\n\t\t\t\tapiType: helmChartType,\n\t\t\t\tobject:  helmChartAdapter{&sourcev1.HelmChart{}},\n\t\t\t\tforce:   true,\n\t\t\t}, srcRef\n\t\tcase sourcev1.OCIRepositoryKind:\n\t\t\treturn reconcileCommand{\n\t\t\t\tapiType: ociRepositoryType,\n\t\t\t\tobject:  ociRepositoryAdapter{&sourcev1.OCIRepository{}},\n\t\t\t}, srcRef\n\t\tdefault:\n\t\t\treturn nil, srcRef\n\t\t}\n\tdefault:\n\t\t// default case assumes the HelmRelease is using a HelmChartTemplate\n\t\tns = obj.Spec.Chart.Spec.SourceRef.Namespace\n\t\tif ns == \"\" {\n\t\t\tns = obj.Namespace\n\t\t}\n\t\tname = fmt.Sprintf(\"%s-%s\", obj.Namespace, obj.Name)\n\t\treturn reconcileWithSourceCommand{\n\t\t\t\tapiType: helmChartType,\n\t\t\t\tobject:  helmChartAdapter{&sourcev1.HelmChart{}},\n\t\t\t\tforce:   true,\n\t\t\t}, sourceReference{\n\t\t\t\tkind:      sourcev1.HelmChartKind,\n\t\t\t\tname:      name,\n\t\t\t\tnamespace: ns,\n\t\t\t}\n\t}\n}\n\nfunc (obj helmReleaseAdapter) isStatic() bool {\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_image.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar reconcileImageCmd = &cobra.Command{\n\tUse:   \"image\",\n\tShort: \"Reconcile image automation objects\",\n\tLong:  `The reconcile sub-commands trigger a reconciliation of image automation objects.`,\n}\n\nfunc init() {\n\treconcileCmd.AddCommand(reconcileImageCmd)\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_image_policy.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar reconcileImagePolicyCmd = &cobra.Command{\n\tUse:   \"policy [name]\",\n\tShort: \"Reconcile an ImagePolicy\",\n\tLong:  `The reconcile image policy command triggers a reconciliation of an ImagePolicy resource and waits for it to finish.`,\n\tExample: `\n\t# Trigger a reconciliation for an existing image policy called 'alpine'\n\tflux reconcile image policy alpine`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),\n\tRunE: reconcileCommand{\n\t\tapiType: imagePolicyType,\n\t\tobject:  imagePolicyAdapter{&imagev1.ImagePolicy{}},\n\t}.run,\n}\n\nfunc init() {\n\treconcileImageCmd.AddCommand(reconcileImagePolicyCmd)\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_image_repository.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar reconcileImageRepositoryCmd = &cobra.Command{\n\tUse:   \"repository [name]\",\n\tShort: \"Reconcile an ImageRepository\",\n\tLong:  `The reconcile image repository command triggers a reconciliation of an ImageRepository resource and waits for it to finish.`,\n\tExample: `  # Trigger an scan for an existing image repository\n  flux reconcile image repository alpine`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),\n\tRunE: reconcileCommand{\n\t\tapiType: imageRepositoryType,\n\t\tobject:  imageRepositoryAdapter{&imagev1.ImageRepository{}},\n\t}.run,\n}\n\nfunc init() {\n\treconcileImageCmd.AddCommand(reconcileImageRepositoryCmd)\n}\n\nfunc (obj imageRepositoryAdapter) lastHandledReconcileRequest() string {\n\treturn obj.Status.GetLastHandledReconcileRequest()\n}\n\nfunc (obj imageRepositoryAdapter) successMessage() string {\n\treturn fmt.Sprintf(\"scan fetched %d tags\", obj.Status.LastScanResult.TagCount)\n}\n\nfunc (obj imageRepositoryAdapter) isStatic() bool {\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_image_updateauto.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\tapimeta \"k8s.io/apimachinery/pkg/api/meta\"\n\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\tmeta \"github.com/fluxcd/pkg/apis/meta\"\n)\n\nvar reconcileImageUpdateCmd = &cobra.Command{\n\tUse:   \"update [name]\",\n\tShort: \"Reconcile an ImageUpdateAutomation\",\n\tLong:  `The reconcile image update command triggers a reconciliation of an ImageUpdateAutomation resource and waits for it to finish.`,\n\tExample: `  # Trigger an automation run for an existing image update automation\n  flux reconcile image update latest-images`,\n\tValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),\n\tRunE: reconcileCommand{\n\t\tapiType: imageUpdateAutomationType,\n\t\tobject:  imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},\n\t}.run,\n}\n\nfunc init() {\n\treconcileImageCmd.AddCommand(reconcileImageUpdateCmd)\n}\n\nfunc (obj imageUpdateAutomationAdapter) lastHandledReconcileRequest() string {\n\treturn obj.Status.GetLastHandledReconcileRequest()\n}\n\nfunc (obj imageUpdateAutomationAdapter) successMessage() string {\n\tif rc := apimeta.FindStatusCondition(obj.Status.Conditions, meta.ReadyCondition); rc != nil {\n\t\treturn rc.Message\n\t}\n\tif obj.Status.LastAutomationRunTime != nil {\n\t\treturn \"last run \" + obj.Status.LastAutomationRunTime.Time.Format(time.RFC3339)\n\t}\n\treturn \"automation not yet run\"\n}\n\nfunc (obj imageUpdateAutomationAdapter) isStatic() bool {\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_kustomization.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar reconcileKsCmd = &cobra.Command{\n\tUse:     \"kustomization [name]\",\n\tAliases: []string{\"ks\"},\n\tShort:   \"Reconcile a Kustomization resource\",\n\tLong: `\nThe reconcile kustomization command triggers a reconciliation of a Kustomization resource and waits for it to finish.`,\n\tExample: `  # Trigger a Kustomization apply outside of the reconciliation interval\n  flux reconcile kustomization podinfo\n\n  # Trigger a sync of the Kustomization's source and apply changes\n  flux reconcile kustomization podinfo --with-source`,\n\tValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),\n\tRunE: reconcileWithSourceCommand{\n\t\tapiType: kustomizationType,\n\t\tobject:  kustomizationAdapter{&kustomizev1.Kustomization{}},\n\t}.run,\n}\n\ntype reconcileKsFlags struct {\n\tsyncKsWithSource bool\n}\n\nvar rksArgs reconcileKsFlags\n\nfunc init() {\n\treconcileKsCmd.Flags().BoolVar(&rksArgs.syncKsWithSource, \"with-source\", false, \"reconcile Kustomization source\")\n\n\treconcileCmd.AddCommand(reconcileKsCmd)\n}\n\nfunc (obj kustomizationAdapter) lastHandledReconcileRequest() string {\n\treturn obj.Status.GetLastHandledReconcileRequest()\n}\n\nfunc (obj kustomizationAdapter) reconcileSource() bool {\n\treturn rksArgs.syncKsWithSource\n}\n\nfunc (obj kustomizationAdapter) getSource() (reconcileSource, sourceReference) {\n\tvar cmd reconcileSource\n\tswitch obj.Spec.SourceRef.Kind {\n\tcase sourcev1.OCIRepositoryKind:\n\t\tcmd = reconcileCommand{\n\t\t\tapiType: ociRepositoryType,\n\t\t\tobject:  ociRepositoryAdapter{&sourcev1.OCIRepository{}},\n\t\t}\n\tcase sourcev1.GitRepositoryKind:\n\t\tcmd = reconcileCommand{\n\t\t\tapiType: gitRepositoryType,\n\t\t\tobject:  gitRepositoryAdapter{&sourcev1.GitRepository{}},\n\t\t}\n\tcase sourcev1.BucketKind:\n\t\tcmd = reconcileCommand{\n\t\t\tapiType: bucketType,\n\t\t\tobject:  bucketAdapter{&sourcev1.Bucket{}},\n\t\t}\n\t}\n\n\treturn cmd, sourceReference{\n\t\tkind:      obj.Spec.SourceRef.Kind,\n\t\tname:      obj.Spec.SourceRef.Name,\n\t\tnamespace: obj.Spec.SourceRef.Namespace,\n\t}\n}\n\nfunc (obj kustomizationAdapter) isStatic() bool {\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_receiver.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n)\n\nvar reconcileReceiverCmd = &cobra.Command{\n\tUse:   \"receiver [name]\",\n\tShort: \"Reconcile a Receiver\",\n\tLong:  `The reconcile receiver command triggers a reconciliation of a Receiver resource and waits for it to finish.`,\n\tExample: `  # Trigger a reconciliation for an existing receiver\n  flux reconcile receiver main`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),\n\tRunE: reconcileCommand{\n\t\tapiType: receiverType,\n\t\tobject:  receiverAdapter{&notificationv1.Receiver{}},\n\t}.run,\n}\n\nfunc init() {\n\treconcileCmd.AddCommand(reconcileReceiverCmd)\n}\n\nfunc (obj receiverAdapter) lastHandledReconcileRequest() string {\n\treturn obj.Status.GetLastHandledReconcileRequest()\n}\n\nfunc (obj receiverAdapter) isStatic() bool {\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_source.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar reconcileSourceCmd = &cobra.Command{\n\tUse:   \"source\",\n\tShort: \"Reconcile sources\",\n\tLong:  `The reconcile source sub-commands trigger a reconciliation of sources.`,\n}\n\nfunc init() {\n\treconcileCmd.AddCommand(reconcileSourceCmd)\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_source_bucket.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar reconcileSourceBucketCmd = &cobra.Command{\n\tUse:   \"bucket [name]\",\n\tShort: \"Reconcile a Bucket source\",\n\tLong:  `The reconcile source command triggers a reconciliation of a Bucket resource and waits for it to finish.`,\n\tExample: `  # Trigger a reconciliation for an existing source\n  flux reconcile source bucket podinfo`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),\n\tRunE: reconcileCommand{\n\t\tapiType: bucketType,\n\t\tobject:  bucketAdapter{&sourcev1.Bucket{}},\n\t}.run,\n}\n\nfunc init() {\n\treconcileSourceCmd.AddCommand(reconcileSourceBucketCmd)\n}\n\nfunc (obj bucketAdapter) lastHandledReconcileRequest() string {\n\treturn obj.Status.GetLastHandledReconcileRequest()\n}\n\nfunc (obj bucketAdapter) successMessage() string {\n\treturn fmt.Sprintf(\"fetched revision %s\", obj.Status.Artifact.Revision)\n}\n\nfunc (obj bucketAdapter) isStatic() bool {\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_source_chart.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar reconcileSourceHelmChartCmd = &cobra.Command{\n\tUse:   \"chart [name]\",\n\tShort: \"Reconcile a HelmChart source\",\n\tLong:  `The reconcile source command triggers a reconciliation of a HelmChart resource and waits for it to finish.`,\n\tExample: `  # Trigger a reconciliation for an existing source\n  flux reconcile source chart podinfo\n \n  # Trigger a reconciliation of the HelmCharts's source and apply changes\n  flux reconcile helmchart podinfo --with-source`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),\n\tRunE: reconcileWithSourceCommand{\n\t\tapiType: helmChartType,\n\t\tobject:  helmChartAdapter{&sourcev1.HelmChart{}},\n\t}.run,\n}\n\nfunc init() {\n\treconcileSourceHelmChartCmd.Flags().BoolVar(&rhcArgs.syncHrWithSource, \"with-source\", false, \"reconcile HelmChart source\")\n\treconcileSourceCmd.AddCommand(reconcileSourceHelmChartCmd)\n}\n\nfunc (obj helmChartAdapter) lastHandledReconcileRequest() string {\n\treturn obj.Status.GetLastHandledReconcileRequest()\n}\n\ntype reconcileHelmChartFlags struct {\n\tsyncHrWithSource bool\n}\n\nvar rhcArgs reconcileHelmChartFlags\n\nfunc (obj helmChartAdapter) reconcileSource() bool {\n\treturn rhcArgs.syncHrWithSource\n}\n\nfunc (obj helmChartAdapter) getSource() (reconcileSource, sourceReference) {\n\tvar cmd reconcileSource\n\tswitch obj.Spec.SourceRef.Kind {\n\tcase sourcev1.HelmRepositoryKind:\n\t\tcmd = reconcileCommand{\n\t\t\tapiType: helmRepositoryType,\n\t\t\tobject:  helmRepositoryAdapter{&sourcev1.HelmRepository{}},\n\t\t}\n\tcase sourcev1.GitRepositoryKind:\n\t\tcmd = reconcileCommand{\n\t\t\tapiType: gitRepositoryType,\n\t\t\tobject:  gitRepositoryAdapter{&sourcev1.GitRepository{}},\n\t\t}\n\tcase sourcev1.BucketKind:\n\t\tcmd = reconcileCommand{\n\t\t\tapiType: bucketType,\n\t\t\tobject:  bucketAdapter{&sourcev1.Bucket{}},\n\t\t}\n\t}\n\n\treturn cmd, sourceReference{\n\t\tkind:      obj.Spec.SourceRef.Kind,\n\t\tname:      obj.Spec.SourceRef.Name,\n\t\tnamespace: obj.Namespace,\n\t}\n}\n\nfunc (obj helmChartAdapter) isStatic() bool {\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_source_git.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar reconcileSourceGitCmd = &cobra.Command{\n\tUse:   \"git [name]\",\n\tShort: \"Reconcile a GitRepository source\",\n\tLong:  `The reconcile source command triggers a reconciliation of a GitRepository resource and waits for it to finish.`,\n\tExample: `  # Trigger a git pull for an existing source\n  flux reconcile source git podinfo`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),\n\tRunE: reconcileCommand{\n\t\tapiType: gitRepositoryType,\n\t\tobject:  gitRepositoryAdapter{&sourcev1.GitRepository{}},\n\t}.run,\n}\n\nfunc init() {\n\treconcileSourceCmd.AddCommand(reconcileSourceGitCmd)\n}\n\nfunc (obj gitRepositoryAdapter) lastHandledReconcileRequest() string {\n\treturn obj.Status.GetLastHandledReconcileRequest()\n}\n\nfunc (obj gitRepositoryAdapter) successMessage() string {\n\treturn fmt.Sprintf(\"fetched revision %s\", obj.Status.Artifact.Revision)\n}\n\nfunc (obj gitRepositoryAdapter) isStatic() bool {\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_source_helm.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\t\"github.com/fluxcd/pkg/runtime/conditions\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar reconcileSourceHelmCmd = &cobra.Command{\n\tUse:   \"helm [name]\",\n\tShort: \"Reconcile a HelmRepository source\",\n\tLong:  `The reconcile source command triggers a reconciliation of a HelmRepository resource and waits for it to finish.`,\n\tExample: `  # Trigger a reconciliation for an existing source\n  flux reconcile source helm podinfo`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),\n\tRunE: reconcileCommand{\n\t\tapiType: helmRepositoryType,\n\t\tobject:  helmRepositoryAdapter{&sourcev1.HelmRepository{}},\n\t}.run,\n}\n\nfunc init() {\n\treconcileSourceCmd.AddCommand(reconcileSourceHelmCmd)\n}\n\nfunc (obj helmRepositoryAdapter) lastHandledReconcileRequest() string {\n\treturn obj.Status.GetLastHandledReconcileRequest()\n}\n\nfunc (obj helmRepositoryAdapter) successMessage() string {\n\t// HelmRepository of type OCI don't set an Artifact\n\tif obj.Spec.Type == sourcev1.HelmRepositoryTypeOCI {\n\t\treadyCondition := conditions.Get(obj.HelmRepository, meta.ReadyCondition)\n\t\t// This shouldn't happen, successMessage shouldn't be called if\n\t\t// object isn't ready\n\t\tif readyCondition == nil {\n\t\t\treturn \"\"\n\t\t}\n\t\treturn readyCondition.Message\n\t}\n\treturn fmt.Sprintf(\"fetched revision %s\", obj.Status.Artifact.Revision)\n}\n\nfunc (obj helmRepositoryAdapter) isStatic() bool {\n\treturn obj.Spec.Type == sourcev1.HelmRepositoryTypeOCI\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_source_oci.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar reconcileSourceOCIRepositoryCmd = &cobra.Command{\n\tUse:   \"oci [name]\",\n\tShort: \"Reconcile an OCIRepository\",\n\tLong:  `The reconcile source command triggers a reconciliation of an OCIRepository resource and waits for it to finish.`,\n\tExample: `  # Trigger a reconciliation for an existing source\n  flux reconcile source oci podinfo`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),\n\tRunE: reconcileCommand{\n\t\tapiType: ociRepositoryType,\n\t\tobject:  ociRepositoryAdapter{&sourcev1.OCIRepository{}},\n\t}.run,\n}\n\nfunc init() {\n\treconcileSourceCmd.AddCommand(reconcileSourceOCIRepositoryCmd)\n}\n\nfunc (obj ociRepositoryAdapter) lastHandledReconcileRequest() string {\n\treturn obj.Status.GetLastHandledReconcileRequest()\n}\n\nfunc (obj ociRepositoryAdapter) successMessage() string {\n\treturn fmt.Sprintf(\"fetched revision %s\", obj.Status.Artifact.Revision)\n}\n\nfunc (obj ociRepositoryAdapter) isStatic() bool {\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flux/reconcile_with_source.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\tapimeta \"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\ntype sourceReference struct {\n\tkind      string\n\tname      string\n\tnamespace string\n}\n\ntype reconcileWithSource interface {\n\tadapter\n\treconcilable\n\treconcileSource() bool\n\tgetSource() (reconcileSource, sourceReference)\n}\n\ntype reconcileSource interface {\n\trun(cmd *cobra.Command, args []string) error\n}\n\ntype reconcileWithSourceCommand struct {\n\tapiType\n\tobject reconcileWithSource\n\tforce  bool\n}\n\nfunc (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"%s name is required\", reconcile.kind)\n\t}\n\tname := args[0]\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\tName:      name,\n\t}\n\n\terr = kubeClient.Get(ctx, namespacedName, reconcile.object.asClientObject())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif reconcile.object.isSuspended() {\n\t\treturn fmt.Errorf(\"resource is suspended\")\n\t}\n\n\tif reconcile.object.reconcileSource() || reconcile.force {\n\t\treconcileCmd, srcRef := reconcile.object.getSource()\n\t\tif reconcileCmd == nil {\n\t\t\treturn fmt.Errorf(\"cannot reconcile source of kind %s\", srcRef.kind)\n\t\t}\n\n\t\tnsCopy := *kubeconfigArgs.Namespace\n\t\tif srcRef.namespace != \"\" {\n\t\t\t*kubeconfigArgs.Namespace = srcRef.namespace\n\t\t}\n\n\t\tif err := reconcileCmd.run(nil, []string{srcRef.name}); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t*kubeconfigArgs.Namespace = nsCopy\n\t}\n\n\tlastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()\n\tlogger.Actionf(\"annotating %s %s in %s namespace\", reconcile.kind, name, *kubeconfigArgs.Namespace)\n\tif err := requestReconciliation(ctx, kubeClient, namespacedName,\n\t\treconcile.groupVersion.WithKind(reconcile.kind)); err != nil {\n\t\treturn err\n\t}\n\tlogger.Successf(\"%s annotated\", reconcile.kind)\n\n\tlogger.Waitingf(\"waiting for %s reconciliation\", reconcile.kind)\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,\n\t\treconciliationHandled(kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {\n\t\treturn err\n\t}\n\n\treadyCond := apimeta.FindStatusCondition(reconcilableConditions(reconcile.object), meta.ReadyCondition)\n\tif readyCond == nil {\n\t\treturn fmt.Errorf(\"status can't be determined\")\n\t}\n\n\tif readyCond.Status != metav1.ConditionTrue {\n\t\treturn fmt.Errorf(\"%s reconciliation failed: %s\", reconcile.kind, readyCond.Message)\n\t}\n\tlogger.Successf(\"%s\", reconcile.object.successMessage())\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/resume.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sort\"\n\t\"sync\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar resumeCmd = &cobra.Command{\n\tUse:   \"resume\",\n\tShort: \"Resume suspended resources\",\n\tLong:  `The resume sub-commands resume a suspended resource.`,\n}\n\ntype ResumeFlags struct {\n\tall  bool\n\twait bool\n}\n\nvar resumeArgs ResumeFlags\n\nfunc init() {\n\tresumeCmd.PersistentFlags().BoolVarP(&resumeArgs.all, \"all\", \"\", false,\n\t\t\"resume all resources in that namespace\")\n\tresumeCmd.PersistentFlags().BoolVarP(&resumeArgs.wait, \"wait\", \"\", false,\n\t\t\"waits for one resource to reconcile before moving to the next one\")\n\trootCmd.AddCommand(resumeCmd)\n}\n\ntype resumable interface {\n\tadapter\n\tcopyable\n\tstatusable\n\tsetUnsuspended()\n\tisStatic() bool\n\tsuccessMessage() string\n}\n\ntype resumeCommand struct {\n\tapiType\n\tclient          client.WithWatch\n\tlist            listResumable\n\tnamespace       string\n\tshouldReconcile bool\n}\n\ntype listResumable interface {\n\tlistAdapter\n\tresumeItem(i int) resumable\n}\n\ntype reconcileResponse struct {\n\tresumable\n\terr error\n}\n\nfunc (resume resumeCommand) run(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 && !resumeArgs.all {\n\t\treturn fmt.Errorf(\"%s name is required\", resume.humanKind)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresume.client = kubeClient\n\tresume.namespace = *kubeconfigArgs.Namespace\n\n\t// require waiting for the object(s) if the user has not provided the --wait flag and gave exactly\n\t// one object to resume. This is necessary to maintain backwards compatibility with prior versions\n\t// of this command. Otherwise just follow the value of the --wait flag (including its default).\n\tresume.shouldReconcile = !resumeCmd.PersistentFlags().Changed(\"wait\") && len(args) == 1 || resumeArgs.wait\n\n\tresumables, err := resume.getPatchedResumables(ctx, args)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(len(resumables))\n\n\tresultChan := make(chan reconcileResponse, len(resumables))\n\tfor _, r := range resumables {\n\t\tgo func(res resumable) {\n\t\t\tdefer wg.Done()\n\t\t\tresultChan <- resume.reconcile(ctx, res)\n\t\t}(r)\n\t}\n\n\tgo func() {\n\t\tdefer close(resultChan)\n\t\twg.Wait()\n\t}()\n\n\treconcileResps := make([]reconcileResponse, 0, len(resumables))\n\tfor c := range resultChan {\n\t\treconcileResps = append(reconcileResps, c)\n\t}\n\n\tresume.printMessage(reconcileResps)\n\n\t// Return an error if any reconciliation failed\n\tvar failedCount int\n\tfor _, r := range reconcileResps {\n\t\tif r.resumable != nil && r.err != nil {\n\t\t\tfailedCount++\n\t\t}\n\t}\n\tif failedCount > 0 {\n\t\treturn fmt.Errorf(\"reconciliation failed for %d %s(s)\", failedCount, resume.kind)\n\t}\n\n\treturn nil\n}\n\n// getPatchedResumables returns a list of the given resumable objects that have been patched to be resumed.\n// If the args slice is empty, it patches all resumable objects in the given namespace.\nfunc (resume *resumeCommand) getPatchedResumables(ctx context.Context, args []string) ([]resumable, error) {\n\tif len(args) < 1 {\n\t\tobjs, err := resume.patch(ctx, args, []client.ListOption{\n\t\t\tclient.InNamespace(resume.namespace),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed patching objects: %w\", err)\n\t\t}\n\n\t\treturn objs, nil\n\t}\n\n\tvar resumables []resumable\n\tprocessed := make(map[string]struct{}, len(args))\n\tfor _, arg := range args {\n\t\tif _, has := processed[arg]; has {\n\t\t\tcontinue // skip object that user might have provided more than once\n\t\t}\n\t\tprocessed[arg] = struct{}{}\n\n\t\tobjs, err := resume.patch(ctx, args, []client.ListOption{\n\t\t\tclient.InNamespace(resume.namespace),\n\t\t\tclient.MatchingFields{\n\t\t\t\t\"metadata.name\": arg,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tresumables = append(resumables, objs...)\n\t}\n\n\treturn resumables, nil\n}\n\n// Patches resumable objects by setting their status to unsuspended.\n// Returns a slice of resumables that have been patched and any error encountered during patching.\nfunc (resume resumeCommand) patch(ctx context.Context, args []string, listOpts []client.ListOption) ([]resumable, error) {\n\tif err := resume.client.List(ctx, resume.list.asClientList(), listOpts...); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif resume.list.len() == 0 {\n\t\tif len(args) < 1 {\n\t\t\tlogger.Failuref(\"no %s objects found in %s namespace\", resume.kind, resume.namespace)\n\t\t} else {\n\t\t\tlogger.Failuref(\"%s object '%s' not found in %s namespace\", resume.kind, args[0], resume.namespace)\n\t\t}\n\t\treturn nil, nil\n\t}\n\n\tvar resumables []resumable\n\n\tfor i := 0; i < resume.list.len(); i++ {\n\t\tobj := resume.list.resumeItem(i)\n\t\tlogger.Actionf(\"resuming %s %s in %s namespace\", resume.humanKind, obj.asClientObject().GetName(), resume.namespace)\n\n\t\tpatch := client.MergeFrom(obj.deepCopyClientObject())\n\t\tobj.setUnsuspended()\n\t\tif err := resume.client.Patch(ctx, obj.asClientObject(), patch); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tresumables = append(resumables, obj)\n\n\t\tlogger.Successf(\"%s resumed\", resume.humanKind)\n\t}\n\n\treturn resumables, nil\n}\n\n// Waits for resumable object to be reconciled and returns the object and any error encountered while waiting.\n// Returns an empty reconcileResponse, if shouldReconcile is false.\nfunc (resume resumeCommand) reconcile(ctx context.Context, res resumable) reconcileResponse {\n\tif !resume.shouldReconcile {\n\t\treturn reconcileResponse{}\n\t}\n\n\tnamespacedName := types.NamespacedName{\n\t\tName:      res.asClientObject().GetName(),\n\t\tNamespace: resume.namespace,\n\t}\n\n\tlogger.Waitingf(\"waiting for %s reconciliation\", resume.kind)\n\n\treadyConditionFunc := isObjectReadyConditionFunc(resume.client, namespacedName, res.asClientObject())\n\tif res.isStatic() {\n\t\treadyConditionFunc = isStaticObjectReadyConditionFunc(resume.client, namespacedName, res.asClientObject())\n\t}\n\n\tif err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, readyConditionFunc); err != nil {\n\t\treturn reconcileResponse{\n\t\t\tresumable: res,\n\t\t\terr:       err,\n\t\t}\n\t}\n\n\treturn reconcileResponse{\n\t\tresumable: res,\n\t\terr:       nil,\n\t}\n}\n\n// Sorts the given reconcileResponses by resumable name and prints the success/error message for each response.\nfunc (resume resumeCommand) printMessage(responses []reconcileResponse) {\n\tsort.Slice(responses, func(i, j int) bool {\n\t\tr1, r2 := responses[i], responses[j]\n\t\tif r1.resumable == nil || r2.resumable == nil {\n\t\t\treturn false\n\t\t}\n\t\treturn r1.asClientObject().GetName() <= r2.asClientObject().GetName()\n\t})\n\n\t// Print success/error message.\n\tfor _, r := range responses {\n\t\tif r.resumable == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif r.err != nil {\n\t\t\tlogger.Failuref(\"%s %s reconciliation failed: %s\", resume.kind, r.asClientObject().GetName(), r.err.Error())\n\t\t\tcontinue\n\t\t}\n\t\tlogger.Successf(\"%s %s reconciliation completed\", resume.kind, r.asClientObject().GetName())\n\t\tlogger.Successf(\"%s\", r.successMessage())\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/resume_alert.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\nvar resumeAlertCmd = &cobra.Command{\n\tUse:   \"alert [name]\",\n\tShort: \"Resume a suspended Alert\",\n\tLong: `The resume command marks a previously suspended Alert resource for reconciliation and waits for it to\nfinish the apply.`,\n\tExample: `  # Resume reconciliation for an existing Alert\n  flux resume alert main\n\n  # Resume reconciliation for multiple Alerts\n  flux resume alert main-1 main-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),\n\tRunE: resumeCommand{\n\t\tapiType: alertType,\n\t\tlist:    &alertListAdapter{&notificationv1.AlertList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeCmd.AddCommand(resumeAlertCmd)\n}\n\nfunc (obj alertAdapter) getObservedGeneration() int64 {\n\treturn 0\n}\n\nfunc (obj alertAdapter) setUnsuspended() {\n\tobj.Alert.Spec.Suspend = false\n}\n\nfunc (obj alertAdapter) successMessage() string {\n\treturn \"Alert reconciliation completed\"\n}\n\nfunc (a alertAdapter) isStatic() bool {\n\treturn true\n}\n\nfunc (a alertListAdapter) resumeItem(i int) resumable {\n\treturn &alertAdapter{&a.AlertList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/resume_alertprovider.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\nvar resumeAlertProviderCmd = &cobra.Command{\n\tUse:   \"alert-provider [name]\",\n\tShort: \"Resume a suspended Provider\",\n\tLong: `The resume command marks a previously suspended Provider resource for reconciliation and waits for it to\nfinish the apply.`,\n\tExample: `  # Resume reconciliation for an existing Provider\n  flux resume alert-provider main\n\n  # Resume reconciliation for multiple Providers\n  flux resume alert-provider main-1 main-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),\n\tRunE: resumeCommand{\n\t\tapiType: alertProviderType,\n\t\tlist:    &alertProviderListAdapter{&notificationv1.ProviderList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeCmd.AddCommand(resumeAlertProviderCmd)\n}\n\nfunc (obj alertProviderAdapter) getObservedGeneration() int64 {\n\treturn 0\n}\n\nfunc (obj alertProviderAdapter) setUnsuspended() {\n\tobj.Provider.Spec.Suspend = false\n}\n\nfunc (obj alertProviderAdapter) successMessage() string {\n\treturn \"Provider reconciliation completed\"\n}\n\nfunc (a alertProviderAdapter) isStatic() bool {\n\treturn true\n}\n\nfunc (a alertProviderListAdapter) resumeItem(i int) resumable {\n\treturn &alertProviderAdapter{&a.ProviderList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/resume_helmrelease.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n)\n\nvar resumeHrCmd = &cobra.Command{\n\tUse:     \"helmrelease [name]\",\n\tAliases: []string{\"hr\"},\n\tShort:   \"Resume a suspended HelmRelease\",\n\tLong: `The resume command marks a previously suspended HelmRelease resource for reconciliation and waits for it to\nfinish the apply.`,\n\tExample: `  # Resume reconciliation for an existing Helm release\n  flux resume hr podinfo\n\n  # Resume reconciliation for multiple Helm releases\n  flux resume hr podinfo-1 podinfo-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),\n\tRunE: resumeCommand{\n\t\tapiType: helmReleaseType,\n\t\tlist:    helmReleaseListAdapter{&helmv2.HelmReleaseList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeCmd.AddCommand(resumeHrCmd)\n}\n\nfunc (obj helmReleaseAdapter) getObservedGeneration() int64 {\n\treturn obj.HelmRelease.Status.ObservedGeneration\n}\n\nfunc (obj helmReleaseAdapter) setUnsuspended() {\n\tobj.HelmRelease.Spec.Suspend = false\n}\n\nfunc (obj helmReleaseAdapter) successMessage() string {\n\treturn fmt.Sprintf(\"applied revision %s\", getHelmReleaseRevision(*obj.HelmRelease))\n}\n\nfunc (a helmReleaseListAdapter) resumeItem(i int) resumable {\n\treturn &helmReleaseAdapter{&a.HelmReleaseList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/resume_image.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar resumeImageCmd = &cobra.Command{\n\tUse:   \"image\",\n\tShort: \"Resume image automation objects\",\n\tLong:  `The resume image sub-commands resume suspended image automation objects.`,\n}\n\nfunc init() {\n\tresumeCmd.AddCommand(resumeImageCmd)\n}\n"
  },
  {
    "path": "cmd/flux/resume_image_policy.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar resumeImagePolicyCmd = &cobra.Command{\n\tUse:   \"policy [name]\",\n\tShort: \"Resume an ImagePolicy\",\n\tLong:  `The resume image policy command resumes a suspended ImagePolicy resource.`,\n\tExample: `\n\t# Resume a suspended image policy called 'alpine'\n\tflux resume image policy alpine`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),\n\tRunE: resumeCommand{\n\t\tapiType: imagePolicyType,\n\t\tlist:    imagePolicyListAdapter{&imagev1.ImagePolicyList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeImageCmd.AddCommand(resumeImagePolicyCmd)\n}\n"
  },
  {
    "path": "cmd/flux/resume_image_repository.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar resumeImageRepositoryCmd = &cobra.Command{\n\tUse:   \"repository [name]\",\n\tShort: \"Resume a suspended ImageRepository\",\n\tLong:  `The resume command marks a previously suspended ImageRepository resource for reconciliation and waits for it to finish.`,\n\tExample: `  # Resume reconciliation for an existing ImageRepository\n  flux resume image repository alpine\n\n  # Resume reconciliation for multiple ImageRepositories\n  flux resume image repository alpine-1 alpine-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),\n\tRunE: resumeCommand{\n\t\tapiType: imageRepositoryType,\n\t\tlist:    imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeImageCmd.AddCommand(resumeImageRepositoryCmd)\n}\n\nfunc (obj imageRepositoryAdapter) getObservedGeneration() int64 {\n\treturn obj.ImageRepository.Status.ObservedGeneration\n}\n\nfunc (obj imageRepositoryAdapter) setUnsuspended() {\n\tobj.ImageRepository.Spec.Suspend = false\n}\n\nfunc (a imageRepositoryListAdapter) resumeItem(i int) resumable {\n\treturn &imageRepositoryAdapter{&a.ImageRepositoryList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/resume_image_updateauto.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n)\n\nvar resumeImageUpdateCmd = &cobra.Command{\n\tUse:   \"update [name]\",\n\tShort: \"Resume a suspended ImageUpdateAutomation\",\n\tLong:  `The resume command marks a previously suspended ImageUpdateAutomation resource for reconciliation and waits for it to finish.`,\n\tExample: `  # Resume reconciliation for an existing ImageUpdateAutomation\n  flux resume image update latest-images\n\n  # Resume reconciliation for multiple ImageUpdateAutomations\n  flux resume image update latest-images-1 latest-images-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),\n\tRunE: resumeCommand{\n\t\tapiType: imageUpdateAutomationType,\n\t\tlist:    imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeImageCmd.AddCommand(resumeImageUpdateCmd)\n}\n\nfunc (obj imageUpdateAutomationAdapter) setUnsuspended() {\n\tobj.ImageUpdateAutomation.Spec.Suspend = false\n}\n\nfunc (obj imageUpdateAutomationAdapter) getObservedGeneration() int64 {\n\treturn obj.ImageUpdateAutomation.Status.ObservedGeneration\n}\n\nfunc (a imageUpdateAutomationListAdapter) resumeItem(i int) resumable {\n\treturn &imageUpdateAutomationAdapter{&a.ImageUpdateAutomationList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/resume_kustomization.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n)\n\nvar resumeKsCmd = &cobra.Command{\n\tUse:     \"kustomization [name]\",\n\tAliases: []string{\"ks\"},\n\tShort:   \"Resume a suspended Kustomization\",\n\tLong: `The resume command marks a previously suspended Kustomization resource for reconciliation and waits for it to\nfinish the apply.`,\n\tExample: `  # Resume reconciliation for an existing Kustomization\n  flux resume ks podinfo\n\n  # Resume reconciliation for multiple Kustomizations\n  flux resume ks podinfo-1 podinfo-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),\n\tRunE: resumeCommand{\n\t\tapiType: kustomizationType,\n\t\tlist:    kustomizationListAdapter{&kustomizev1.KustomizationList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeCmd.AddCommand(resumeKsCmd)\n}\n\nfunc (obj kustomizationAdapter) getObservedGeneration() int64 {\n\treturn obj.Kustomization.Status.ObservedGeneration\n}\n\nfunc (obj kustomizationAdapter) setUnsuspended() {\n\tobj.Kustomization.Spec.Suspend = false\n}\n\nfunc (obj kustomizationAdapter) successMessage() string {\n\treturn fmt.Sprintf(\"applied revision %s\", obj.Status.LastAppliedRevision)\n}\n\nfunc (a kustomizationListAdapter) resumeItem(i int) resumable {\n\treturn &kustomizationAdapter{&a.KustomizationList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/resume_receiver.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n)\n\nvar resumeReceiverCmd = &cobra.Command{\n\tUse:   \"receiver [name]\",\n\tShort: \"Resume a suspended Receiver\",\n\tLong: `The resume command marks a previously suspended Receiver resource for reconciliation and waits for it to\nfinish the apply.`,\n\tExample: `  # Resume reconciliation for an existing Receiver\n  flux resume receiver main\n\n  # Resume reconciliation for multiple Receivers\n  flux resume receiver main-1 main-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),\n\tRunE: resumeCommand{\n\t\tapiType: receiverType,\n\t\tlist:    receiverListAdapter{&notificationv1.ReceiverList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeCmd.AddCommand(resumeReceiverCmd)\n}\n\nfunc (obj receiverAdapter) getObservedGeneration() int64 {\n\treturn obj.Receiver.Status.ObservedGeneration\n}\n\nfunc (obj receiverAdapter) setUnsuspended() {\n\tobj.Receiver.Spec.Suspend = false\n}\n\nfunc (obj receiverAdapter) successMessage() string {\n\treturn \"Receiver reconciliation completed\"\n}\n\nfunc (a receiverListAdapter) resumeItem(i int) resumable {\n\treturn &receiverAdapter{&a.ReceiverList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/resume_source.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar resumeSourceCmd = &cobra.Command{\n\tUse:   \"source\",\n\tShort: \"Resume sources\",\n\tLong:  `The resume sub-commands resume a suspended source.`,\n}\n\nfunc init() {\n\tresumeCmd.AddCommand(resumeSourceCmd)\n}\n"
  },
  {
    "path": "cmd/flux/resume_source_bucket.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar resumeSourceBucketCmd = &cobra.Command{\n\tUse:   \"bucket [name]\",\n\tShort: \"Resume a suspended Bucket\",\n\tLong:  `The resume command marks a previously suspended Bucket resource for reconciliation and waits for it to finish.`,\n\tExample: `  # Resume reconciliation for an existing Bucket\n  flux resume source bucket podinfo\n\n  # Resume reconciliation for multiple Buckets\n  flux resume source bucket podinfo-1 podinfo-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),\n\tRunE: resumeCommand{\n\t\tapiType: bucketType,\n\t\tlist:    bucketListAdapter{&sourcev1.BucketList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeSourceCmd.AddCommand(resumeSourceBucketCmd)\n}\n\nfunc (obj bucketAdapter) getObservedGeneration() int64 {\n\treturn obj.Bucket.Status.ObservedGeneration\n}\n\nfunc (obj bucketAdapter) setUnsuspended() {\n\tobj.Bucket.Spec.Suspend = false\n}\n\nfunc (a bucketListAdapter) resumeItem(i int) resumable {\n\treturn &bucketAdapter{&a.BucketList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/resume_source_chart.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar resumeSourceHelmChartCmd = &cobra.Command{\n\tUse:   \"chart [name]\",\n\tShort: \"Resume a suspended HelmChart\",\n\tLong:  `The resume command marks a previously suspended HelmChart resource for reconciliation and waits for it to finish.`,\n\tExample: `  # Resume reconciliation for an existing HelmChart\n  flux resume source chart podinfo\n\n  # Resume reconciliation for multiple HelmCharts\n  flux resume source chart podinfo-1 podinfo-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),\n\tRunE: resumeCommand{\n\t\tapiType: helmChartType,\n\t\tlist:    &helmChartListAdapter{&sourcev1.HelmChartList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeSourceCmd.AddCommand(resumeSourceHelmChartCmd)\n}\n\nfunc (obj helmChartAdapter) getObservedGeneration() int64 {\n\treturn obj.HelmChart.Status.ObservedGeneration\n}\n\nfunc (obj helmChartAdapter) setUnsuspended() {\n\tobj.HelmChart.Spec.Suspend = false\n}\n\nfunc (obj helmChartAdapter) successMessage() string {\n\treturn fmt.Sprintf(\"fetched revision %s\", obj.Status.Artifact.Revision)\n}\n\nfunc (a helmChartListAdapter) resumeItem(i int) resumable {\n\treturn &helmChartAdapter{&a.HelmChartList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/resume_source_git.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar resumeSourceGitCmd = &cobra.Command{\n\tUse:   \"git [name]\",\n\tShort: \"Resume a suspended GitRepository\",\n\tLong:  `The resume command marks a previously suspended GitRepository resource for reconciliation and waits for it to finish.`,\n\tExample: `  # Resume reconciliation for an existing GitRepository\n  flux resume source git podinfo\n  \n  # Resume reconciliation for multiple GitRepositories\n  flux resume source git podinfo-1 podinfo-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),\n\tRunE: resumeCommand{\n\t\tapiType: gitRepositoryType,\n\t\tlist:    gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeSourceCmd.AddCommand(resumeSourceGitCmd)\n}\n\nfunc (obj gitRepositoryAdapter) getObservedGeneration() int64 {\n\treturn obj.GitRepository.Status.ObservedGeneration\n}\n\nfunc (obj gitRepositoryAdapter) setUnsuspended() {\n\tobj.GitRepository.Spec.Suspend = false\n}\n\nfunc (a gitRepositoryListAdapter) resumeItem(i int) resumable {\n\treturn &gitRepositoryAdapter{&a.GitRepositoryList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/resume_source_helm.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar resumeSourceHelmCmd = &cobra.Command{\n\tUse:   \"helm [name]\",\n\tShort: \"Resume a suspended HelmRepository\",\n\tLong:  `The resume command marks a previously suspended HelmRepository resource for reconciliation and waits for it to finish.`,\n\tExample: `  # Resume reconciliation for an existing HelmRepository\n  flux resume source helm bitnami\n\n  # Resume reconciliation for multiple HelmRepositories\n  flux resume source helm bitnami-1 bitnami-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),\n\tRunE: resumeCommand{\n\t\tapiType: helmRepositoryType,\n\t\tlist:    helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeSourceCmd.AddCommand(resumeSourceHelmCmd)\n}\n\nfunc (obj helmRepositoryAdapter) getObservedGeneration() int64 {\n\treturn obj.HelmRepository.Status.ObservedGeneration\n}\n\nfunc (obj helmRepositoryAdapter) setUnsuspended() {\n\tobj.HelmRepository.Spec.Suspend = false\n}\n\nfunc (a helmRepositoryListAdapter) resumeItem(i int) resumable {\n\treturn &helmRepositoryAdapter{&a.HelmRepositoryList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/resume_source_oci.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar resumeSourceOCIRepositoryCmd = &cobra.Command{\n\tUse:   \"oci [name]\",\n\tShort: \"Resume a suspended OCIRepository\",\n\tLong:  `The resume command marks a previously suspended OCIRepository resource for reconciliation and waits for it to finish.`,\n\tExample: `  # Resume reconciliation for an existing OCIRepository\n  flux resume source oci podinfo\n\n  # Resume reconciliation for multiple OCIRepositories\n  flux resume source oci podinfo-1 podinfo-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),\n\tRunE: resumeCommand{\n\t\tapiType: ociRepositoryType,\n\t\tlist:    ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},\n\t}.run,\n}\n\nfunc init() {\n\tresumeSourceCmd.AddCommand(resumeSourceOCIRepositoryCmd)\n}\n\nfunc (obj ociRepositoryAdapter) getObservedGeneration() int64 {\n\treturn obj.OCIRepository.Status.ObservedGeneration\n}\n\nfunc (obj ociRepositoryAdapter) setUnsuspended() {\n\tobj.OCIRepository.Spec.Suspend = false\n}\n\nfunc (a ociRepositoryListAdapter) resumeItem(i int) resumable {\n\treturn &ociRepositoryAdapter{&a.OCIRepositoryList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/source.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\n// These are general-purpose adapters for attaching methods to, for\n// the various commands. The *List adapters implement len(), since\n// it's used in at least a couple of commands.\n\n// sourcev1.ociRepository\n\nvar ociRepositoryType = apiType{\n\tkind:         sourcev1.OCIRepositoryKind,\n\thumanKind:    \"source oci\",\n\tgroupVersion: sourcev1.GroupVersion,\n}\n\ntype ociRepositoryAdapter struct {\n\t*sourcev1.OCIRepository\n}\n\nfunc (a ociRepositoryAdapter) asClientObject() client.Object {\n\treturn a.OCIRepository\n}\n\nfunc (a ociRepositoryAdapter) deepCopyClientObject() client.Object {\n\treturn a.OCIRepository.DeepCopy()\n}\n\n// sourcev1b2.OCIRepositoryList\n\ntype ociRepositoryListAdapter struct {\n\t*sourcev1.OCIRepositoryList\n}\n\nfunc (a ociRepositoryListAdapter) asClientList() client.ObjectList {\n\treturn a.OCIRepositoryList\n}\n\nfunc (a ociRepositoryListAdapter) len() int {\n\treturn len(a.OCIRepositoryList.Items)\n}\n\n// sourcev1.Bucket\n\nvar bucketType = apiType{\n\tkind:         sourcev1.BucketKind,\n\thumanKind:    \"source bucket\",\n\tgroupVersion: sourcev1.GroupVersion,\n}\n\ntype bucketAdapter struct {\n\t*sourcev1.Bucket\n}\n\nfunc (a bucketAdapter) asClientObject() client.Object {\n\treturn a.Bucket\n}\n\nfunc (a bucketAdapter) deepCopyClientObject() client.Object {\n\treturn a.Bucket.DeepCopy()\n}\n\n// sourcev1.BucketList\n\ntype bucketListAdapter struct {\n\t*sourcev1.BucketList\n}\n\nfunc (a bucketListAdapter) asClientList() client.ObjectList {\n\treturn a.BucketList\n}\n\nfunc (a bucketListAdapter) len() int {\n\treturn len(a.BucketList.Items)\n}\n\n// sourcev1.HelmChart\n\nvar helmChartType = apiType{\n\tkind:         sourcev1.HelmChartKind,\n\thumanKind:    \"source chart\",\n\tgroupVersion: sourcev1.GroupVersion,\n}\n\ntype helmChartAdapter struct {\n\t*sourcev1.HelmChart\n}\n\nfunc (a helmChartAdapter) asClientObject() client.Object {\n\treturn a.HelmChart\n}\n\nfunc (a helmChartAdapter) deepCopyClientObject() client.Object {\n\treturn a.HelmChart.DeepCopy()\n}\n\n// sourcev1.HelmChartList\n\ntype helmChartListAdapter struct {\n\t*sourcev1.HelmChartList\n}\n\nfunc (a helmChartListAdapter) asClientList() client.ObjectList {\n\treturn a.HelmChartList\n}\n\nfunc (a helmChartListAdapter) len() int {\n\treturn len(a.HelmChartList.Items)\n}\n\n// sourcev1.GitRepository\n\nvar gitRepositoryType = apiType{\n\tkind:         sourcev1.GitRepositoryKind,\n\thumanKind:    \"source git\",\n\tgroupVersion: sourcev1.GroupVersion,\n}\n\ntype gitRepositoryAdapter struct {\n\t*sourcev1.GitRepository\n}\n\nfunc (a gitRepositoryAdapter) asClientObject() client.Object {\n\treturn a.GitRepository\n}\n\nfunc (a gitRepositoryAdapter) deepCopyClientObject() client.Object {\n\treturn a.GitRepository.DeepCopy()\n}\n\n// sourcev1.GitRepositoryList\n\ntype gitRepositoryListAdapter struct {\n\t*sourcev1.GitRepositoryList\n}\n\nfunc (a gitRepositoryListAdapter) asClientList() client.ObjectList {\n\treturn a.GitRepositoryList\n}\n\nfunc (a gitRepositoryListAdapter) len() int {\n\treturn len(a.GitRepositoryList.Items)\n}\n\n// sourcev1.HelmRepository\n\nvar helmRepositoryType = apiType{\n\tkind:         sourcev1.HelmRepositoryKind,\n\thumanKind:    \"source helm\",\n\tgroupVersion: sourcev1.GroupVersion,\n}\n\ntype helmRepositoryAdapter struct {\n\t*sourcev1.HelmRepository\n}\n\nfunc (a helmRepositoryAdapter) asClientObject() client.Object {\n\treturn a.HelmRepository\n}\n\nfunc (a helmRepositoryAdapter) deepCopyClientObject() client.Object {\n\treturn a.HelmRepository.DeepCopy()\n}\n\n// sourcev1.HelmRepositoryList\n\ntype helmRepositoryListAdapter struct {\n\t*sourcev1.HelmRepositoryList\n}\n\nfunc (a helmRepositoryListAdapter) asClientList() client.ObjectList {\n\treturn a.HelmRepositoryList\n}\n\nfunc (a helmRepositoryListAdapter) len() int {\n\treturn len(a.HelmRepositoryList.Items)\n}\n\n// sourcev1.ExternalArtifact\n\nvar externalArtifactType = apiType{\n\tkind:         sourcev1.ExternalArtifactKind,\n\thumanKind:    \"source external-artifact\",\n\tgroupVersion: sourcev1.GroupVersion,\n}\n\ntype externalArtifactAdapter struct {\n\t*sourcev1.ExternalArtifact\n}\n\nfunc (a externalArtifactAdapter) asClientObject() client.Object {\n\treturn a.ExternalArtifact\n}\n\nfunc (a externalArtifactAdapter) deepCopyClientObject() client.Object {\n\treturn a.ExternalArtifact.DeepCopy()\n}\n\n// sourcev1.ExternalArtifactList\n\ntype externalArtifactListAdapter struct {\n\t*sourcev1.ExternalArtifactList\n}\n\nfunc (a externalArtifactListAdapter) asClientList() client.ObjectList {\n\treturn a.ExternalArtifactList\n}\n\nfunc (a externalArtifactListAdapter) len() int {\n\treturn len(a.ExternalArtifactList.Items)\n}\n"
  },
  {
    "path": "cmd/flux/source_oci_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestSourceOCI(t *testing.T) {\n\tnamespace := allocateNamespace(\"oci-test\")\n\tdel, err := execSetupTestNamespace(namespace)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Cleanup(del)\n\n\ttmpl := map[string]string{\"ns\": namespace}\n\n\tcases := []struct {\n\t\targs       string\n\t\tgoldenFile string\n\t\ttmpl       map[string]string\n\t}{\n\t\t{\n\t\t\t\"create source oci thrfg --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m\",\n\t\t\t\"testdata/oci/create_source_oci.golden\",\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"get source oci thrfg\",\n\t\t\t\"testdata/oci/get_oci.golden\",\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"reconcile source oci thrfg\",\n\t\t\t\"testdata/oci/reconcile_oci.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"suspend source oci thrfg\",\n\t\t\t\"testdata/oci/suspend_oci.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"resume source oci thrfg\",\n\t\t\t\"testdata/oci/resume_oci.golden\",\n\t\t\ttmpl,\n\t\t},\n\t\t{\n\t\t\t\"delete source oci thrfg --silent\",\n\t\t\t\"testdata/oci/delete_oci.golden\",\n\t\t\ttmpl,\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tcmd := cmdTestCase{\n\t\t\targs:   tc.args + \" -n=\" + namespace,\n\t\t\tassert: assertGoldenTemplateFile(tc.goldenFile, tc.tmpl),\n\t\t}\n\t\tcmd.runTestCmd(t)\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/stats.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/cli-utils/pkg/kstatus/status\"\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n\tnotificationv1b3 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/printers\"\n)\n\nvar statsCmd = &cobra.Command{\n\tUse:   \"stats\",\n\tArgs:  cobra.NoArgs,\n\tShort: \"Stats of Flux reconciles\",\n\tLong: withPreviewNote(`The stats command prints a report of Flux custom resources present on a cluster,\nincluding their reconcile status and the amount of cumulative storage used for each source type`),\n\tExample: `  # Print the stats report for a namespace\n  flux stats --namespace default\n\n  #  Print the stats report for the whole cluster\n  flux stats -A`,\n\tRunE: runStatsCmd,\n}\n\ntype StatsFlags struct {\n\tallNamespaces bool\n}\n\nvar statsArgs StatsFlags\n\nfunc init() {\n\tstatsCmd.PersistentFlags().BoolVarP(&statsArgs.allNamespaces, \"all-namespaces\", \"A\", false,\n\t\t\"list the statistics for objects across all namespaces\")\n\trootCmd.AddCommand(statsCmd)\n}\n\nfunc runStatsCmd(cmd *cobra.Command, args []string) error {\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttypes := []metav1.GroupVersionKind{\n\t\t{\n\t\t\tKind:    sourcev1.GitRepositoryKind,\n\t\t\tVersion: sourcev1.GroupVersion.Version,\n\t\t\tGroup:   sourcev1.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    sourcev1.OCIRepositoryKind,\n\t\t\tVersion: sourcev1.GroupVersion.Version,\n\t\t\tGroup:   sourcev1.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    sourcev1.HelmRepositoryKind,\n\t\t\tVersion: sourcev1.GroupVersion.Version,\n\t\t\tGroup:   sourcev1.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    sourcev1.HelmChartKind,\n\t\t\tVersion: sourcev1.GroupVersion.Version,\n\t\t\tGroup:   sourcev1.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    sourcev1.BucketKind,\n\t\t\tVersion: sourcev1.GroupVersion.Version,\n\t\t\tGroup:   sourcev1.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    kustomizev1.KustomizationKind,\n\t\t\tVersion: kustomizev1.GroupVersion.Version,\n\t\t\tGroup:   kustomizev1.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    helmv2.HelmReleaseKind,\n\t\t\tVersion: helmv2.GroupVersion.Version,\n\t\t\tGroup:   helmv2.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    notificationv1b3.AlertKind,\n\t\t\tVersion: notificationv1b3.GroupVersion.Version,\n\t\t\tGroup:   notificationv1b3.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    notificationv1b3.ProviderKind,\n\t\t\tVersion: notificationv1b3.GroupVersion.Version,\n\t\t\tGroup:   notificationv1b3.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    notificationv1.ReceiverKind,\n\t\t\tVersion: notificationv1.GroupVersion.Version,\n\t\t\tGroup:   notificationv1.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    autov1.ImageUpdateAutomationKind,\n\t\t\tVersion: autov1.GroupVersion.Version,\n\t\t\tGroup:   autov1.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    imagev1.ImagePolicyKind,\n\t\t\tVersion: imagev1.GroupVersion.Version,\n\t\t\tGroup:   imagev1.GroupVersion.Group,\n\t\t},\n\t\t{\n\t\t\tKind:    imagev1.ImageRepositoryKind,\n\t\t\tVersion: imagev1.GroupVersion.Version,\n\t\t\tGroup:   imagev1.GroupVersion.Group,\n\t\t},\n\t}\n\n\theader := []string{\"Reconcilers\", \"Running\", \"Failing\", \"Suspended\", \"Storage\"}\n\tvar rows [][]string\n\n\tfor _, t := range types {\n\t\tvar total int\n\t\tvar suspended int\n\t\tvar failing int\n\t\tvar totalSize int64\n\n\t\tlist := unstructured.UnstructuredList{\n\t\t\tObject: map[string]interface{}{\n\t\t\t\t\"apiVersion\": t.Group + \"/\" + t.Version,\n\t\t\t\t\"kind\":       t.Kind,\n\t\t\t},\n\t\t}\n\n\t\tscope := client.InNamespace(\"\")\n\t\tif !statsArgs.allNamespaces {\n\t\t\tscope = client.InNamespace(*kubeconfigArgs.Namespace)\n\t\t}\n\n\t\tif err := kubeClient.List(ctx, &list, scope); err == nil {\n\t\t\ttotal = len(list.Items)\n\n\t\t\tfor _, item := range list.Items {\n\t\t\t\tif s, _, _ := unstructured.NestedBool(item.Object, \"spec\", \"suspend\"); s {\n\t\t\t\t\tsuspended++\n\t\t\t\t}\n\n\t\t\t\tif obj, err := status.GetObjectWithConditions(item.Object); err == nil {\n\t\t\t\t\tfor _, cond := range obj.Status.Conditions {\n\t\t\t\t\t\tif cond.Type == \"Ready\" && cond.Status == corev1.ConditionFalse {\n\t\t\t\t\t\t\tfailing++\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif size, found, _ := unstructured.NestedInt64(item.Object, \"status\", \"artifact\", \"size\"); found {\n\t\t\t\t\ttotalSize += size\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\trows = append(rows, []string{\n\t\t\tt.Kind,\n\t\t\tformatInt(total - suspended),\n\t\t\tformatInt(failing),\n\t\t\tformatInt(suspended),\n\t\t\tformatSize(totalSize),\n\t\t})\n\t}\n\n\terr = printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc formatInt(i int) string {\n\treturn fmt.Sprintf(\"%d\", i)\n}\n\nfunc formatSize(b int64) string {\n\tif b == 0 {\n\t\treturn \"-\"\n\t}\n\tconst unit = 1024\n\tif b < unit {\n\t\treturn fmt.Sprintf(\"%d B\", b)\n\t}\n\tdiv, exp := int64(unit), 0\n\tfor n := b / unit; n >= unit; n /= unit {\n\t\tdiv *= unit\n\t\texp++\n\t}\n\treturn fmt.Sprintf(\"%.1f %ciB\",\n\t\tfloat64(b)/float64(div), \"KMGTPE\"[exp])\n}\n"
  },
  {
    "path": "cmd/flux/status.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/fluxcd/cli-utils/pkg/object\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\n// statusable is used to see if a resource is considered ready in the usual way\ntype statusable interface {\n\tadapter\n\t// this is implemented by ObjectMeta\n\tGetGeneration() int64\n\tgetObservedGeneration() int64\n}\n\n// oldConditions represents the deprecated API which is sunsetting.\ntype oldConditions interface {\n\t// this is usually implemented by GOTK API objects because it's used by pkg/apis/meta\n\tGetStatusConditions() *[]metav1.Condition\n}\n\nfunc buildComponentObjectRefs(components ...string) ([]object.ObjMetadata, error) {\n\tvar objRefs []object.ObjMetadata\n\tfor _, deployment := range components {\n\t\tobjRefs = append(objRefs, object.ObjMetadata{\n\t\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\t\tName:      deployment,\n\t\t\tGroupKind: schema.GroupKind{Group: \"apps\", Kind: \"Deployment\"},\n\t\t})\n\t}\n\treturn objRefs, nil\n}\n"
  },
  {
    "path": "cmd/flux/suspend.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar suspendCmd = &cobra.Command{\n\tUse:   \"suspend\",\n\tShort: \"Suspend resources\",\n\tLong:  `The suspend sub-commands suspend the reconciliation of a resource.`,\n}\n\ntype SuspendFlags struct {\n\tall bool\n}\n\nvar suspendArgs SuspendFlags\n\nfunc init() {\n\tsuspendCmd.PersistentFlags().BoolVarP(&suspendArgs.all, \"all\", \"\", false,\n\t\t\"suspend all resources in that namespace\")\n\trootCmd.AddCommand(suspendCmd)\n}\n\ntype suspendable interface {\n\tadapter\n\tcopyable\n\tisSuspended() bool\n\tsetSuspended()\n}\n\ntype suspendCommand struct {\n\tapiType\n\tlist   listSuspendable\n\tobject suspendable\n}\n\ntype listSuspendable interface {\n\tlistAdapter\n\titem(i int) suspendable\n}\n\nfunc (suspend suspendCommand) run(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 && !suspendArgs.all {\n\t\treturn fmt.Errorf(\"%s name is required\", suspend.humanKind)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(args) < 1 && suspendArgs.all {\n\t\tlistOpts := []client.ListOption{\n\t\t\tclient.InNamespace(*kubeconfigArgs.Namespace),\n\t\t}\n\n\t\tif err := suspend.patch(ctx, kubeClient, listOpts); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tprocessed := make(map[string]struct{}, len(args))\n\tfor _, arg := range args {\n\t\tif _, has := processed[arg]; has {\n\t\t\tcontinue // skip object that user might have provided more than once\n\t\t}\n\t\tprocessed[arg] = struct{}{}\n\n\t\tlistOpts := []client.ListOption{\n\t\t\tclient.InNamespace(*kubeconfigArgs.Namespace),\n\t\t\tclient.MatchingFields{\n\t\t\t\t\"metadata.name\": arg,\n\t\t\t},\n\t\t}\n\n\t\tif err := suspend.patch(ctx, kubeClient, listOpts); err != nil {\n\t\t\tif err == ErrNoObjectsFound {\n\t\t\t\tlogger.Failuref(\"%s %s not found in %s namespace\", suspend.kind, arg, *kubeconfigArgs.Namespace)\n\t\t\t} else {\n\t\t\t\tlogger.Failuref(\"failed suspending %s %s in %s namespace: %s\", suspend.kind, arg, *kubeconfigArgs.Namespace, err.Error())\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nvar ErrNoObjectsFound = errors.New(\"no objects found\")\n\nfunc (suspend suspendCommand) patch(ctx context.Context, kubeClient client.WithWatch, listOpts []client.ListOption) error {\n\tif err := kubeClient.List(ctx, suspend.list.asClientList(), listOpts...); err != nil {\n\t\treturn err\n\t}\n\n\tif suspend.list.len() == 0 {\n\t\treturn ErrNoObjectsFound\n\t}\n\n\tfor i := 0; i < suspend.list.len(); i++ {\n\t\tlogger.Actionf(\"suspending %s %s in %s namespace\", suspend.humanKind, suspend.list.item(i).asClientObject().GetName(), *kubeconfigArgs.Namespace)\n\n\t\tobj := suspend.list.item(i)\n\t\tpatch := client.MergeFrom(obj.deepCopyClientObject())\n\t\tobj.setSuspended()\n\t\tif err := kubeClient.Patch(ctx, obj.asClientObject(), patch); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tlogger.Successf(\"%s suspended\", suspend.humanKind)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/suspend_alert.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\nvar suspendAlertCmd = &cobra.Command{\n\tUse:   \"alert [name]\",\n\tShort: \"Suspend reconciliation of Alert\",\n\tLong:  `The suspend command disables the reconciliation of a Alert resource.`,\n\tExample: `  # Suspend reconciliation for an existing Alert\n  flux suspend alert main\n\n  # Suspend reconciliation for multiple Alerts\n  flux suspend alert main-1 main-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),\n\tRunE: suspendCommand{\n\t\tapiType: alertType,\n\t\tobject:  &alertAdapter{&notificationv1.Alert{}},\n\t\tlist:    &alertListAdapter{&notificationv1.AlertList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendCmd.AddCommand(suspendAlertCmd)\n}\n\nfunc (obj alertAdapter) isSuspended() bool {\n\treturn obj.Alert.Spec.Suspend\n}\n\nfunc (obj alertAdapter) setSuspended() {\n\tobj.Alert.Spec.Suspend = true\n}\n\nfunc (a alertListAdapter) item(i int) suspendable {\n\treturn &alertAdapter{&a.AlertList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/suspend_alertprovider.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n)\n\nvar suspendAlertProviderCmd = &cobra.Command{\n\tUse:   \"alert-provider [name]\",\n\tShort: \"Suspend reconciliation of Provider\",\n\tLong:  `The suspend command disables the reconciliation of a Provider resource.`,\n\tExample: `  # Suspend reconciliation for an existing Provider\n  flux suspend alert-provider main\n\n  # Suspend reconciliation for multiple Providers\n  flux suspend alert-providers main-1 main-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),\n\tRunE: suspendCommand{\n\t\tapiType: alertProviderType,\n\t\tobject:  &alertProviderAdapter{&notificationv1.Provider{}},\n\t\tlist:    &alertProviderListAdapter{&notificationv1.ProviderList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendCmd.AddCommand(suspendAlertProviderCmd)\n}\n\nfunc (obj alertProviderAdapter) isSuspended() bool {\n\treturn obj.Provider.Spec.Suspend\n}\n\nfunc (obj alertProviderAdapter) setSuspended() {\n\tobj.Provider.Spec.Suspend = true\n}\n\nfunc (a alertProviderListAdapter) item(i int) suspendable {\n\treturn &alertProviderAdapter{&a.ProviderList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/suspend_helmrelease.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n)\n\nvar suspendHrCmd = &cobra.Command{\n\tUse:     \"helmrelease [name]\",\n\tAliases: []string{\"hr\"},\n\tShort:   \"Suspend reconciliation of HelmRelease\",\n\tLong:    `The suspend command disables the reconciliation of a HelmRelease resource.`,\n\tExample: `  # Suspend reconciliation for an existing Helm release\n  flux suspend hr podinfo\n\n  # Suspend reconciliation for multiple Helm releases\n  flux suspend hr podinfo-1 podinfo-2 `,\n\tValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),\n\tRunE: suspendCommand{\n\t\tapiType: helmReleaseType,\n\t\tobject:  &helmReleaseAdapter{&helmv2.HelmRelease{}},\n\t\tlist:    &helmReleaseListAdapter{&helmv2.HelmReleaseList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendCmd.AddCommand(suspendHrCmd)\n}\n\nfunc (obj helmReleaseAdapter) isSuspended() bool {\n\treturn obj.HelmRelease.Spec.Suspend\n}\n\nfunc (obj helmReleaseAdapter) setSuspended() {\n\tobj.HelmRelease.Spec.Suspend = true\n}\n\nfunc (a helmReleaseListAdapter) item(i int) suspendable {\n\treturn &helmReleaseAdapter{&a.HelmReleaseList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/suspend_image.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar suspendImageCmd = &cobra.Command{\n\tUse:   \"image\",\n\tShort: \"Suspend image automation objects\",\n\tLong:  `The suspend image sub-commands suspend the reconciliation of an image automation object.`,\n}\n\nfunc init() {\n\tsuspendCmd.AddCommand(suspendImageCmd)\n}\n"
  },
  {
    "path": "cmd/flux/suspend_image_policy.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar suspendImagePolicyCmd = &cobra.Command{\n\tUse:               \"policy [name]\",\n\tShort:             \"Suspend an ImagePolicy\",\n\tLong:              `The suspend image policy command suspends the reconciliation of an ImagePolicy resource.`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),\n\tRunE: suspendCommand{\n\t\tapiType: imagePolicyType,\n\t\tlist:    imagePolicyListAdapter{&imagev1.ImagePolicyList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendImageCmd.AddCommand(suspendImagePolicyCmd)\n}\n"
  },
  {
    "path": "cmd/flux/suspend_image_repository.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n)\n\nvar suspendImageRepositoryCmd = &cobra.Command{\n\tUse:   \"repository [name]\",\n\tShort: \"Suspend reconciliation of an ImageRepository\",\n\tLong:  `The suspend image repository command disables the reconciliation of a ImageRepository resource.`,\n\tExample: `  # Suspend reconciliation for an existing ImageRepository\n  flux suspend image repository alpine\n\n  # Suspend reconciliation for multiple ImageRepositories\n  flux suspend image repository alpine-1 alpine-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),\n\tRunE: suspendCommand{\n\t\tapiType: imageRepositoryType,\n\t\tobject:  imageRepositoryAdapter{&imagev1.ImageRepository{}},\n\t\tlist:    &imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendImageCmd.AddCommand(suspendImageRepositoryCmd)\n}\n\nfunc (obj imageRepositoryAdapter) isSuspended() bool {\n\treturn obj.ImageRepository.Spec.Suspend\n}\n\nfunc (obj imageRepositoryAdapter) setSuspended() {\n\tobj.ImageRepository.Spec.Suspend = true\n}\n\nfunc (a imageRepositoryListAdapter) item(i int) suspendable {\n\treturn &imageRepositoryAdapter{&a.ImageRepositoryList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/suspend_image_updateauto.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n)\n\nvar suspendImageUpdateCmd = &cobra.Command{\n\tUse:   \"update [name]\",\n\tShort: \"Suspend reconciliation of an ImageUpdateAutomation\",\n\tLong:  `The suspend image update command disables the reconciliation of a ImageUpdateAutomation resource.`,\n\tExample: `  # Suspend reconciliation for an existing ImageUpdateAutomation\n  flux suspend image update latest-images\n\n  # Suspend reconciliation for multiple ImageUpdateAutomations\n  flux suspend image update latest-images-1 latest-images-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),\n\tRunE: suspendCommand{\n\t\tapiType: imageUpdateAutomationType,\n\t\tobject:  imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},\n\t\tlist:    &imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendImageCmd.AddCommand(suspendImageUpdateCmd)\n}\n\nfunc (update imageUpdateAutomationAdapter) isSuspended() bool {\n\treturn update.ImageUpdateAutomation.Spec.Suspend\n}\n\nfunc (update imageUpdateAutomationAdapter) setSuspended() {\n\tupdate.ImageUpdateAutomation.Spec.Suspend = true\n}\n\nfunc (a imageUpdateAutomationListAdapter) item(i int) suspendable {\n\treturn &imageUpdateAutomationAdapter{&a.ImageUpdateAutomationList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/suspend_kustomization.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n)\n\nvar suspendKsCmd = &cobra.Command{\n\tUse:     \"kustomization [name]\",\n\tAliases: []string{\"ks\"},\n\tShort:   \"Suspend reconciliation of Kustomization\",\n\tLong:    `The suspend command disables the reconciliation of a Kustomization resource.`,\n\tExample: `  # Suspend reconciliation for an existing Kustomization\n  flux suspend ks podinfo\n\n  # Suspend reconciliation for multiple Kustomizations\n  flux suspend ks podinfo-1 podinfo-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),\n\tRunE: suspendCommand{\n\t\tapiType: kustomizationType,\n\t\tobject:  kustomizationAdapter{&kustomizev1.Kustomization{}},\n\t\tlist:    &kustomizationListAdapter{&kustomizev1.KustomizationList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendCmd.AddCommand(suspendKsCmd)\n}\n\nfunc (obj kustomizationAdapter) isSuspended() bool {\n\treturn obj.Kustomization.Spec.Suspend\n}\n\nfunc (obj kustomizationAdapter) setSuspended() {\n\tobj.Kustomization.Spec.Suspend = true\n}\n\nfunc (a kustomizationListAdapter) item(i int) suspendable {\n\treturn &kustomizationAdapter{&a.KustomizationList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/suspend_receiver.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n)\n\nvar suspendReceiverCmd = &cobra.Command{\n\tUse:   \"receiver [name]\",\n\tShort: \"Suspend reconciliation of Receiver\",\n\tLong:  `The suspend command disables the reconciliation of a Receiver resource.`,\n\tExample: `  # Suspend reconciliation for an existing Receiver\n  flux suspend receiver main\n\n  # Suspend reconciliation for multiple Receivers\n  flux suspend receiver main-1 main-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),\n\tRunE: suspendCommand{\n\t\tapiType: receiverType,\n\t\tobject:  &receiverAdapter{&notificationv1.Receiver{}},\n\t\tlist:    &receiverListAdapter{&notificationv1.ReceiverList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendCmd.AddCommand(suspendReceiverCmd)\n}\n\nfunc (obj receiverAdapter) isSuspended() bool {\n\treturn obj.Receiver.Spec.Suspend\n}\n\nfunc (obj receiverAdapter) setSuspended() {\n\tobj.Receiver.Spec.Suspend = true\n}\n\nfunc (a receiverListAdapter) item(i int) suspendable {\n\treturn &receiverAdapter{&a.ReceiverList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/suspend_source.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar suspendSourceCmd = &cobra.Command{\n\tUse:   \"source\",\n\tShort: \"Suspend sources\",\n\tLong:  `The suspend sub-commands suspend the reconciliation of a source.`,\n}\n\nfunc init() {\n\tsuspendCmd.AddCommand(suspendSourceCmd)\n}\n"
  },
  {
    "path": "cmd/flux/suspend_source_bucket.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar suspendSourceBucketCmd = &cobra.Command{\n\tUse:   \"bucket [name]\",\n\tShort: \"Suspend reconciliation of a Bucket\",\n\tLong:  `The suspend command disables the reconciliation of a Bucket resource.`,\n\tExample: `  # Suspend reconciliation for an existing Bucket\n  flux suspend source bucket podinfo\n\n  # Suspend reconciliation for multiple Buckets\n  flux suspend source bucket podinfo-1 podinfo-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),\n\tRunE: suspendCommand{\n\t\tapiType: bucketType,\n\t\tobject:  bucketAdapter{&sourcev1.Bucket{}},\n\t\tlist:    bucketListAdapter{&sourcev1.BucketList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendSourceCmd.AddCommand(suspendSourceBucketCmd)\n}\n\nfunc (obj bucketAdapter) isSuspended() bool {\n\treturn obj.Bucket.Spec.Suspend\n}\n\nfunc (obj bucketAdapter) setSuspended() {\n\tobj.Bucket.Spec.Suspend = true\n}\n\nfunc (a bucketListAdapter) item(i int) suspendable {\n\treturn &bucketAdapter{&a.BucketList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/suspend_source_chart.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar suspendSourceHelmChartCmd = &cobra.Command{\n\tUse:   \"chart [name]\",\n\tShort: \"Suspend reconciliation of a HelmChart\",\n\tLong:  `The suspend command disables the reconciliation of a HelmChart resource.`,\n\tExample: `  # Suspend reconciliation for an existing HelmChart\n  flux suspend source chart podinfo\n\n  # Suspend reconciliation for multiple HelmCharts\n  flux suspend source chart podinfo-1 podinfo-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),\n\tRunE: suspendCommand{\n\t\tapiType: helmChartType,\n\t\tobject:  helmChartAdapter{&sourcev1.HelmChart{}},\n\t\tlist:    helmChartListAdapter{&sourcev1.HelmChartList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendSourceCmd.AddCommand(suspendSourceHelmChartCmd)\n}\n\nfunc (obj helmChartAdapter) isSuspended() bool {\n\treturn obj.HelmChart.Spec.Suspend\n}\n\nfunc (obj helmChartAdapter) setSuspended() {\n\tobj.HelmChart.Spec.Suspend = true\n}\n\nfunc (a helmChartListAdapter) item(i int) suspendable {\n\treturn &helmChartAdapter{&a.HelmChartList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/suspend_source_git.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar suspendSourceGitCmd = &cobra.Command{\n\tUse:   \"git [name]\",\n\tShort: \"Suspend reconciliation of a GitRepository\",\n\tLong:  `The suspend command disables the reconciliation of a GitRepository resource.`,\n\tExample: `  # Suspend reconciliation for an existing GitRepository\n  flux suspend source git podinfo\n\n  # Suspend reconciliation for multiple GitRepositories\n  flux suspend source git podinfo-1 podinfo-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),\n\tRunE: suspendCommand{\n\t\tapiType: gitRepositoryType,\n\t\tobject:  gitRepositoryAdapter{&sourcev1.GitRepository{}},\n\t\tlist:    gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendSourceCmd.AddCommand(suspendSourceGitCmd)\n}\n\nfunc (obj gitRepositoryAdapter) isSuspended() bool {\n\treturn obj.GitRepository.Spec.Suspend\n}\n\nfunc (obj gitRepositoryAdapter) setSuspended() {\n\tobj.GitRepository.Spec.Suspend = true\n}\n\nfunc (a gitRepositoryListAdapter) item(i int) suspendable {\n\treturn &gitRepositoryAdapter{&a.GitRepositoryList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/suspend_source_helm.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar suspendSourceHelmCmd = &cobra.Command{\n\tUse:   \"helm [name]\",\n\tShort: \"Suspend reconciliation of a HelmRepository\",\n\tLong:  `The suspend command disables the reconciliation of a HelmRepository resource.`,\n\tExample: `  # Suspend reconciliation for an existing HelmRepository\n  flux suspend source helm bitnami\n\n  # Suspend reconciliation for multiple HelmRepositories\n  flux suspend source helm bitnami-1 bitnami-2 `,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),\n\tRunE: suspendCommand{\n\t\tapiType: helmRepositoryType,\n\t\tobject:  helmRepositoryAdapter{&sourcev1.HelmRepository{}},\n\t\tlist:    helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendSourceCmd.AddCommand(suspendSourceHelmCmd)\n}\n\nfunc (obj helmRepositoryAdapter) isSuspended() bool {\n\treturn obj.HelmRepository.Spec.Suspend\n}\n\nfunc (obj helmRepositoryAdapter) setSuspended() {\n\tobj.HelmRepository.Spec.Suspend = true\n}\n\nfunc (a helmRepositoryListAdapter) item(i int) suspendable {\n\treturn &helmRepositoryAdapter{&a.HelmRepositoryList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/suspend_source_oci.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar suspendSourceOCIRepositoryCmd = &cobra.Command{\n\tUse:   \"oci [name]\",\n\tShort: \"Suspend reconciliation of an OCIRepository\",\n\tLong:  `The suspend command disables the reconciliation of an OCIRepository resource.`,\n\tExample: `  # Suspend reconciliation for an existing OCIRepository\n  flux suspend source oci podinfo\n\n  # Suspend reconciliation for multiple OCIRepositories\n  flux suspend source oci podinfo-1 podinfo-2`,\n\tValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),\n\tRunE: suspendCommand{\n\t\tapiType: ociRepositoryType,\n\t\tobject:  ociRepositoryAdapter{&sourcev1.OCIRepository{}},\n\t\tlist:    ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},\n\t}.run,\n}\n\nfunc init() {\n\tsuspendSourceCmd.AddCommand(suspendSourceOCIRepositoryCmd)\n}\n\nfunc (obj ociRepositoryAdapter) isSuspended() bool {\n\treturn obj.OCIRepository.Spec.Suspend\n}\n\nfunc (obj ociRepositoryAdapter) setSuspended() {\n\tobj.OCIRepository.Spec.Suspend = true\n}\n\nfunc (a ociRepositoryListAdapter) item(i int) suspendable {\n\treturn &ociRepositoryAdapter{&a.OCIRepositoryList.Items[i]}\n}\n"
  },
  {
    "path": "cmd/flux/tag.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar tagCmd = &cobra.Command{\n\tUse:   \"tag\",\n\tShort: \"Tag artifacts\",\n\tLong:  `The tag command is used to tag OCI artifacts.`,\n}\n\nfunc init() {\n\trootCmd.AddCommand(tagCmd)\n}\n"
  },
  {
    "path": "cmd/flux/tag_artifact.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/fluxcd/pkg/oci\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/flags\"\n)\n\nvar tagArtifactCmd = &cobra.Command{\n\tUse:   \"artifact\",\n\tShort: \"Tag artifact\",\n\tLong: `The tag artifact command creates tags for the given OCI artifact.\nThe command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`,\n\tExample: `  # Tag an artifact version as latest\n  flux tag artifact oci://ghcr.io/org/manifests/app:v0.0.1 --tag latest\n`,\n\tRunE: tagArtifactCmdRun,\n}\n\ntype tagArtifactFlags struct {\n\ttags     []string\n\tcreds    string\n\tprovider flags.SourceOCIProvider\n}\n\nvar tagArtifactArgs = newTagArtifactFlags()\n\nfunc newTagArtifactFlags() tagArtifactFlags {\n\treturn tagArtifactFlags{\n\t\tprovider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),\n\t}\n}\n\nfunc init() {\n\ttagArtifactCmd.Flags().StringSliceVar(&tagArtifactArgs.tags, \"tag\", nil, \"tag name\")\n\ttagArtifactCmd.Flags().StringVar(&tagArtifactArgs.creds, \"creds\", \"\", \"credentials for OCI registry in the format <username>[:<password>] if --provider is generic\")\n\ttagArtifactCmd.Flags().Var(&tagArtifactArgs.provider, \"provider\", tagArtifactArgs.provider.Description())\n\ttagCmd.AddCommand(tagArtifactCmd)\n}\n\nfunc tagArtifactCmdRun(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"artifact name is required\")\n\t}\n\tociURL := args[0]\n\n\tif len(tagArtifactArgs.tags) < 1 {\n\t\treturn fmt.Errorf(\"--tag is required\")\n\t}\n\n\turl, err := oci.ParseArtifactURL(ociURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\topts := oci.DefaultOptions()\n\n\tif tagArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {\n\t\tlogger.Actionf(\"logging in to registry with provider credentials\")\n\t\topt, _, err := loginWithProvider(ctx, url, tagArtifactArgs.provider.String())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error during login with provider: %w\", err)\n\t\t}\n\t\topts = append(opts, opt)\n\t}\n\n\tociClient := oci.NewClient(opts)\n\n\tif tagArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && tagArtifactArgs.creds != \"\" {\n\t\tlogger.Actionf(\"logging in to registry with credentials\")\n\t\tif err := ociClient.LoginWithCredentials(tagArtifactArgs.creds); err != nil {\n\t\t\treturn fmt.Errorf(\"could not login with credentials: %w\", err)\n\t\t}\n\t}\n\n\tlogger.Actionf(\"tagging artifact\")\n\n\tfor _, tag := range tagArtifactArgs.tags {\n\t\timg, err := ociClient.Tag(ctx, url, tag)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"tagging artifact failed: %w\", err)\n\t\t}\n\n\t\tlogger.Successf(\"artifact tagged as %s\", img)\n\t}\n\n\treturn nil\n\n}\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/delete-service/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: podinfo\nspec:\n  minReadySeconds: 3\n  revisionHistoryLimit: 5\n  progressDeadlineSeconds: 60\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 0\n    type: RollingUpdate\n  selector:\n    matchLabels:\n      app: podinfo\n  template:\n    metadata:\n      annotations:\n        prometheus.io/scrape: \"true\"\n        prometheus.io/port: \"9797\"\n      labels:\n        app: podinfo\n    spec:\n      containers:\n      - name: podinfod\n        image: ghcr.io/stefanprodan/podinfo:6.0.3\n        imagePullPolicy: IfNotPresent\n        ports:\n        - name: http\n          containerPort: 9898\n          protocol: TCP\n        - name: http-metrics\n          containerPort: 9797\n          protocol: TCP\n        - name: grpc\n          containerPort: 9999\n          protocol: TCP\n        command:\n        - ./podinfo\n        - --port=9898\n        - --port-metrics=9797\n        - --grpc-port=9999\n        - --grpc-service-name=podinfo\n        - --level=info\n        - --random-delay=false\n        - --random-error=false\n        env:\n        - name: PODINFO_UI_COLOR\n          value: \"#34577c\"\n        livenessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/healthz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        readinessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/readyz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        resources:\n          limits:\n            cpu: 2000m\n            memory: 512Mi\n          requests:\n            cpu: 100m\n            memory: 64Mi\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/delete-service/hpa.yaml",
    "content": "apiVersion: autoscaling/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: podinfo\nspec:\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: podinfo\n  minReplicas: 2\n  maxReplicas: 4\n  metrics:\n  - type: Resource\n    resource:\n      name: cpu\n      target:\n        type: Utilization\n        # scale up if usage is above\n        # 99% of the requested CPU (100m)\n        averageUtilization: 99\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/delete-service/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- ./deployment.yaml\n- ./hpa.yaml\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/ignore/.sourceignore",
    "content": "# exclude all\n/*\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/ignore/configmap.yaml",
    "content": "apiVersion: v1\ndata:\n  var: test\nkind: ConfigMap\nmetadata:\n  name: configmap_ignore\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/ignore/not_deployable/ignore_svc.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: do_not_deploy\nspec:\n  type: ClusterIP\n  selector:\n    app: podinfo\n  ports:\n    - name: http\n      port: 9898\n      protocol: TCP\n      targetPort: http\n    - port: 9999\n      targetPort: grpc\n      protocol: TCP\n      name: grpc\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/ignore/secret.yaml",
    "content": "apiVersion: v1\ndata:\n  token: KipTT1BTKio=\nkind: Secret\nmetadata:\n  name: secret_ignore\ntype: Opaque\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/my-app/configmap.yaml",
    "content": "apiVersion: v1\ndata:\n  var: test\nkind: ConfigMap\nmetadata:\n  name: my-app\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: podinfo\nspec:\n  minReadySeconds: 3\n  revisionHistoryLimit: 5\n  progressDeadlineSeconds: 60\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 0\n    type: RollingUpdate\n  selector:\n    matchLabels:\n      app: podinfo\n  template:\n    metadata:\n      annotations:\n        prometheus.io/scrape: \"true\"\n        prometheus.io/port: \"9797\"\n      labels:\n        app: podinfo\n    spec:\n      containers:\n      - name: podinfod\n        image: ghcr.io/stefanprodan/podinfo:6.0.10\n        imagePullPolicy: IfNotPresent\n        ports:\n        - name: http\n          containerPort: 9898\n          protocol: TCP\n        - name: http-metrics\n          containerPort: 9797\n          protocol: TCP\n        - name: grpc\n          containerPort: 9999\n          protocol: TCP\n        command:\n        - ./podinfo\n        - --port=9898\n        - --port-metrics=9797\n        - --grpc-port=9999\n        - --grpc-service-name=podinfo\n        - --level=info\n        - --random-delay=false\n        - --random-error=false\n        env:\n        - name: PODINFO_UI_COLOR\n          value: \"#34577c\"\n        livenessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/healthz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        readinessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/readyz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        resources:\n          limits:\n            cpu: 2000m\n            memory: 512Mi\n          requests:\n            cpu: 100m\n            memory: 64Mi\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo/dockerconfigjson-sops-secret.yaml",
    "content": "apiVersion: v1\ndata:\n  .dockerconfigjson: ENC[AES256_GCM,data:KHCFH3hNnc+PMfWLFEPjebf3W4z4WXbGFAANRZyZC+07z7wlrTALJM6rn8YslW4tMAWCoAYxblC5WRCszTy0h9rw0U/RGOv5H0qCgnNg/FILFUqhwo9pNfrUH+MEP4M9qxxbLKZwObpHUE7DUsKx1JYAxsI=,iv:q48lqUbUQD+0cbYcjNMZMJLRdGHi78ZmDhNAT2th9tg=,tag:QRI2SZZXQrAcdql3R5AH2g==,type:str]\nkind: Secret\nmetadata:\n  name: docker-secret\ntype: kubernetes.io/dockerconfigjson\nsops:\n  kms: []\n  gcp_kms: []\n  azure_kv: []\n  hc_vault: []\n  age:\n    - recipient: age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce\n      enc: |\n        -----BEGIN AGE ENCRYPTED FILE-----\n        YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3eU1CTEJhVXZ4eEVYYkVV\n        OU90TEcrR2pYckttN0pBanJoSUZWSW1RQXlRCkUydFJ3V1NZUTBuVFF0aC9GUEcw\n        bUdhNjJWTkoyL1FUVi9Dc1dxUDBkM0UKLS0tIE1sQXkwcWdGaEFuY0RHQTVXM0J6\n        dWpJcThEbW15V3dXYXpPZklBdW1Hd1kKoIAdmGNPrEctV8h1w8KuvQ5S+BGmgqN9\n        MgpNmUhJjWhgcQpb5BRYpQesBOgU5TBGK7j58A6DMDKlSiYZsdQchQ==\n        -----END AGE ENCRYPTED FILE-----\n  lastmodified: \"2022-02-03T16:03:17Z\"\n  mac: ENC[AES256_GCM,data:AHdYSawajwgAFwlmDN1IPNmT9vWaYKzyVIra2d6sPcjTbZ8/p+VRSRpVm4XZFFsaNnW5AUJaouwXnKYDTmJDXKlr/rQcu9kXqsssQgdzcXaA6l5uJlgsnml8ba7J3OK+iEKMax23mwQEx2EUskCd9ENOwFDkunP02sxqDNOz20k=,iv:8F5OamHt3fAVorf6p+SoIrWoqkcATSGWVoM0EK87S4M=,tag:E1mxXnc7wWkEX5BxhpLtng==,type:str]\n  pgp: []\n  encrypted_regex: ^(data|stringData)$\n  version: 3.7.1\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo/hpa.yaml",
    "content": "apiVersion: autoscaling/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: podinfo\nspec:\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: podinfo\n  minReplicas: 2\n  maxReplicas: 4\n  metrics:\n  - type: Resource\n    resource:\n      name: cpu\n      target:\n        type: Utilization\n        # scale up if usage is above\n        # 99% of the requested CPU (100m)\n        averageUtilization: 99\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- ./deployment.yaml\n- ./hpa.yaml\n- ./service.yaml\n- ./dockerconfigjson-sops-secret.yaml\n- ./stringdata-secret.yaml\nsecretGenerator:\n - files:\n   - token=token.encrypted\n   name: podinfo-token\n - literals:\n   - username=admin\n   - password=1f2d1e2e67df\n   name: db-user-pass\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: podinfo\nspec:\n  type: ClusterIP\n  selector:\n    app: podinfo\n  ports:\n    - name: http\n      port: 9898\n      protocol: TCP\n      targetPort: http\n    - port: 9999\n      targetPort: grpc\n      protocol: TCP\n      name: grpc\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo/stringdata-secret.yaml",
    "content": "apiVersion: v1\nkind: Secret\nmetadata:\n    name: secret-basic-auth-stringdata\ntype: kubernetes.io/basic-auth\nstringData:\n    username: ENC[AES256_GCM,data:uKiQR48=,iv:jh2lgyAVu7igJAgoJsnOGhjxFyvUAa9lvT21u3hhqpU=,tag:zXM2JEpk3ZEH7WfkcWXXkw==,type:str]\n    password: ENC[AES256_GCM,data:PyhZmNhy929JGQ==,iv:PBqPaJmSw21+kn4gIlg5VdjLNZyf613z5RUTCesBoVw=,tag:Hjc7DsuUrtsz7PYPdNkL3g==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJd0xxbDZhYjVoZzY4YWhK\n            d2NvMVgrSGRVUGhHRGg3R1FpVURnbmh1TDBzCjcwby85M3JaK09QVk0yZFNMb2NL\n            c2NQZW5hS1FhYlBHU0VoUzBVYzZYUUUKLS0tIEdaNEw2Y0VjVHpZc3pyYUtLVmJk\n            NmN3K2VLU0NiZ1d0VHBYbGlCM1lrNmMKeWz3yfFbMNE+ly21oLfc1XnDSPRmnlPP\n            wIs8lk/qrzVZ45C9GdWnnPeGZZiia46Yop9TxseUS8gCjJ6KCxJCAg==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2022-02-06T12:51:07Z\"\n    mac: ENC[AES256_GCM,data:jtdzwj19uxdxvnmXg1HkAkDA6XlKMJOYFy7uLI5t/t11LwGop5Yeo7a4nQEEELehRx9J7B6U6NiySxAxBxWx5uW5vI5c8+069VV6dkiCIefnYSzuoIhQafjlFl1/KvH7VEjIWfHYuXF09v9PEKXkxEHUYDpS3QqQ3ymHRRI08pU=,  iv:xX3E7F+AM29Pm8G5oqxRfYu9E7tEBGIaHeCJYgrtFmc=,tag:MJPGusNvu05z939jg8PAwQ==,type:str]\n    pgp: []\n    encrypted_regex: ^(data|stringData)$\n    version: 3.7.1\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo/token.encrypted",
    "content": " {\n     \"data\": \"ENC[AES256_GCM,data:oBe5PlPmfQCUUc4sqKImjw==,iv:MLLEW15QC9kRdVVagJnzLCSk0xZGWIpAeTfHzyxT10g=,tag:K3GkBCGS+ut4Tpk6ndb0CA==,type:str]\",\n     \"sops\": {\n         \"kms\": null,\n         \"gcp_kms\": null,\n         \"azure_kv\": null,\n         \"hc_vault\": null,\n         \"age\": [\n             {\n                 \"recipient\": \"age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce\",\n                 \"enc\": \"-----BEGIN AGE ENCRYPTED FILE-----\\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+                                                                                                                IFgyNTUxOSA1L2RpZHRrK1FSVmYrd1Va\\nY0hxWFQzSDBsT1k3WjNtYmU1QmliaDJycXlNCnF1YjdNOThVbVNvMG9rNS9ZUXZw\\nMnV0bnRUMGNtejFPbzM4U2UzWkszeVkKLS0tIGJ6UGhxMUV3YmVJTHlJSUJpRVRZ\\nVjd0RVRadU8wekxXTHIrYUplYkN2aEEK0I/   MCEtXRk+b/N2G1JF3vHQT24dShWYD\\nw+JIUSA3aLf2sv0zr2MdUEdVWBJoM8nT4D4xVbBORD+669W+9nDeSw==\\n-----END AGE ENCRYPTED FILE-----\\n\"\n             }\n         ],\n         \"lastmodified\": \"2021-11-26T16:34:51Z\",\n         \"mac\": \"ENC[AES256_GCM,data:COGzf5YCHNNP6z4JaEKrjN3M8f5+Q1uKUKTMHwj388/ICmLyi2sSrTmj7PP+X7M9jTVwa8wVgYTpNLiVJx+LcxqvIXM0Tyo+/Cu1zrfao98aiACP8+TSEDiFQNtEus23H+d/X1hqMwRHDI3kQ+                      6scgEGnqY57r3RDSA3E8EhHr4=,iv:LxitVIYm8srZVqFueJh9loClA44Y2Z3XAVYmxesMmOg=,tag:Y8qFD8UGlDfwNSv7xlcn6A==,type:str]\",\n         \"pgp\": null,\n         \"unencrypted_suffix\": \"_unencrypted\",\n         \"version\": \"3.7.1\"\n     }\n }"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo-kustomization.yaml",
    "content": "---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m0s\n  path: ./kustomize\n  force: true\n  prune: true\n  sourceRef:\n    kind: GitRepository\n    name: podinfo\n  targetNamespace: default\n  postBuild:\n    substitute:\n      cluster_env: \"prod\"\n      cluster_region: \"eu-central-1\"\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo-result.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: default\nspec:\n  minReadySeconds: 3\n  progressDeadlineSeconds: 60\n  revisionHistoryLimit: 5\n  selector:\n    matchLabels:\n      app: podinfo\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 0\n    type: RollingUpdate\n  template:\n    metadata:\n      annotations:\n        prometheus.io/port: \"9797\"\n        prometheus.io/scrape: \"true\"\n      labels:\n        app: podinfo\n    spec:\n      containers:\n      - command:\n        - ./podinfo\n        - --port=9898\n        - --port-metrics=9797\n        - --grpc-port=9999\n        - --grpc-service-name=podinfo\n        - --level=info\n        - --random-delay=false\n        - --random-error=false\n        env:\n        - name: PODINFO_UI_COLOR\n          value: '#34577c'\n        image: ghcr.io/stefanprodan/podinfo:6.0.10\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/healthz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        name: podinfod\n        ports:\n        - containerPort: 9898\n          name: http\n          protocol: TCP\n        - containerPort: 9797\n          name: http-metrics\n          protocol: TCP\n        - containerPort: 9999\n          name: grpc\n          protocol: TCP\n        readinessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/readyz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        resources:\n          limits:\n            cpu: 2000m\n            memory: 512Mi\n          requests:\n            cpu: 100m\n            memory: 64Mi\n---\napiVersion: autoscaling/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: default\nspec:\n  maxReplicas: 4\n  metrics:\n  - resource:\n      name: cpu\n      target:\n        averageUtilization: 99\n        type: Utilization\n    type: Resource\n  minReplicas: 2\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: podinfo\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: default\nspec:\n  ports:\n  - name: http\n    port: 9898\n    protocol: TCP\n    targetPort: http\n  - name: grpc\n    port: 9999\n    protocol: TCP\n    targetPort: grpc\n  selector:\n    app: podinfo\n  type: ClusterIP\n---\napiVersion: v1\ndata:\n  .dockerconfigjson: eyJtYXNrIjoiKipTT1BTKioifQ==\nkind: Secret\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: docker-secret\n  namespace: default\ntype: kubernetes.io/dockerconfigjson\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: secret-basic-auth-stringdata\n  namespace: default\nstringData:\n  password: '**SOPS**'\n  username: '**SOPS**'\ntype: kubernetes.io/basic-auth\n---\napiVersion: v1\ndata:\n  token: KipTT1BTKio=\nkind: Secret\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo-token-77t89m9b67\n  namespace: default\ntype: Opaque\n---\napiVersion: v1\ndata:\n  password: MWYyZDFlMmU2N2Rm\n  username: YWRtaW4=\nkind: Secret\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: db-user-pass-bkbd782d2c\n  namespace: default\ntype: Opaque\n---\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo-source.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  interval: 30s\n  ref:\n    branch: master\n  url: https://github.com/stefanprodan/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo-with-ignore-result.yaml",
    "content": "apiVersion: v1\ndata:\n  var: test\nkind: ConfigMap\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: configmap_ignore\n  namespace: default\n---\napiVersion: v1\ndata:\n  token: KipTT1BTKio=\nkind: Secret\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: secret_ignore\n  namespace: default\ntype: Opaque\n---\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo-with-my-app/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- ./my-app.yaml\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo-with-my-app/my-app.yaml",
    "content": "---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: my-app\nspec:\n  interval: 5m0s\n  path: ./my-app\n  force: true\n  prune: true\n  sourceRef:\n    kind: GitRepository\n    name: podinfo\n  targetNamespace: default\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo-with-my-app-result.yaml",
    "content": "apiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: my-app\n  namespace: default\nspec:\n  force: true\n  interval: 5m0s\n  path: ./my-app\n  prune: true\n  sourceRef:\n    kind: GitRepository\n    name: podinfo\n  targetNamespace: default\n---\napiVersion: v1\ndata:\n  var: test\nkind: ConfigMap\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: my-app\n    kustomize.toolkit.fluxcd.io/namespace: default\n  name: my-app\n  namespace: default\n---\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    environment: prod\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n    region: eu-central-1\n  name: podinfo\n  namespace: default\nspec:\n  minReadySeconds: 3\n  progressDeadlineSeconds: 60\n  revisionHistoryLimit: 5\n  selector:\n    matchLabels:\n      app: podinfo\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 0\n    type: RollingUpdate\n  template:\n    metadata:\n      annotations:\n        prometheus.io/port: \"9797\"\n        prometheus.io/scrape: \"true\"\n      labels:\n        app: podinfo\n    spec:\n      containers:\n      - command:\n        - ./podinfo\n        - --port=9898\n        - --port-metrics=9797\n        - --grpc-port=9999\n        - --grpc-service-name=podinfo\n        - --level=info\n        - --random-delay=false\n        - --random-error=false\n        env:\n        - name: PODINFO_UI_COLOR\n          value: '#34577c'\n        image: ghcr.io/stefanprodan/podinfo:6.0.10\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/healthz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        name: podinfod\n        ports:\n        - containerPort: 9898\n          name: http\n          protocol: TCP\n        - containerPort: 9797\n          name: http-metrics\n          protocol: TCP\n        - containerPort: 9999\n          name: grpc\n          protocol: TCP\n        readinessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/readyz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        resources:\n          limits:\n            cpu: 2000m\n            memory: 512Mi\n          requests:\n            cpu: 100m\n            memory: 64Mi\n---\napiVersion: v1\ndata:\n  cluster.json: |\n    {\n      \"annotations\": {\n        \"list\": [\n          {\n            \"builtIn\": 1,\n            \"datasource\": \"-- Grafana --\",\n            \"enable\": true,\n            \"hide\": true,\n            \"iconColor\": \"rgba(0, 211, 255, 1)\",\n            \"name\": \"Annotations & Alerts\",\n            \"type\": \"dashboard\"\n          }\n        ]\n      },\n      \"editable\": true,\n      \"gnetId\": null,\n      \"graphTooltip\": 0,\n      \"iteration\": 1636369574387,\n      \"links\": [],\n      \"panels\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"description\": \"\",\n          \"fieldConfig\": {\n            \"defaults\": {\n              \"decimals\": 0,\n              \"mappings\": [],\n              \"thresholds\": {\n                \"mode\": \"absolute\",\n                \"steps\": [\n                  {\n                    \"color\": \"blue\",\n                    \"value\": null\n                  },\n                  {\n                    \"color\": \"red\",\n                    \"value\": 100\n                  }\n                ]\n              },\n              \"unit\": \"short\"\n            },\n            \"overrides\": []\n          },\n          \"gridPos\": {\n            \"h\": 5,\n            \"w\": 6,\n            \"x\": 0,\n            \"y\": 0\n          },\n          \"id\": 24,\n          \"options\": {\n            \"colorMode\": \"value\",\n            \"graphMode\": \"none\",\n            \"justifyMode\": \"auto\",\n            \"orientation\": \"auto\",\n            \"reduceOptions\": {\n              \"calcs\": [\n                \"last\"\n              ],\n              \"fields\": \"\",\n              \"values\": false\n            },\n            \"text\": {},\n            \"textMode\": \"value\"\n          },\n          \"pluginVersion\": \"7.5.5\",\n          \"targets\": [\n            {\n              \"exemplar\": true,\n              \"expr\": \"count(gotk_reconcile_condition{namespace=~\\\"$operator_namespace\\\",exported_namespace=~\\\"$namespace\\\",type=\\\"Ready\\\",status=\\\"True\\\",kind=~\\\"Kustomization|HelmRelease\\\"})\\n-\\nsum(gotk_reconcile_condition{namespace=~\\\"$operator_namespace\\\",exported_namespace=~\\\"$namespace\\\",type=\\\"Ready\\\",status=\\\"Deleted\\\",kind=~\\\"Kustomization|HelmRelease\\\"})\",\n              \"interval\": \"\",\n              \"legendFormat\": \"\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"timeFrom\": null,\n          \"timeShift\": null,\n          \"title\": \"Cluster Reconcilers\",\n          \"type\": \"stat\"\n        },\n        {\n          \"collapsed\": false,\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"gridPos\": {\n            \"h\": 1,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 9\n          },\n          \"id\": 15,\n          \"panels\": [],\n          \"title\": \"Status\",\n          \"type\": \"row\"\n        }\n      ],\n      \"refresh\": \"\",\n      \"schemaVersion\": 27,\n      \"style\": \"light\",\n      \"tags\": [\n        \"flux\"\n      ],\n      \"time\": {\n        \"from\": \"now-15m\",\n        \"to\": \"now\"\n      },\n      \"timepicker\": {\n        \"refresh_intervals\": [\n          \"10s\",\n          \"30s\",\n          \"1m\",\n          \"5m\",\n          \"15m\",\n          \"30m\",\n          \"1h\",\n          \"2h\",\n          \"1d\"\n        ]\n      },\n      \"timezone\": \"\",\n      \"title\": \"Flux Cluster Stats\",\n      \"uid\": \"flux-cluster\",\n      \"version\": 1\n    }\nkind: ConfigMap\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n    kustomize.toolkit.fluxcd.io/substitute: disabled\n  name: flux-grafana-dashboards-kt8md725kf\n  namespace: default\n---\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/podinfo-without-service-result.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: default\nspec:\n  minReadySeconds: 3\n  progressDeadlineSeconds: 60\n  revisionHistoryLimit: 5\n  selector:\n    matchLabels:\n      app: podinfo\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 0\n    type: RollingUpdate\n  template:\n    metadata:\n      annotations:\n        prometheus.io/port: \"9797\"\n        prometheus.io/scrape: \"true\"\n      labels:\n        app: podinfo\n    spec:\n      containers:\n      - command:\n        - ./podinfo\n        - --port=9898\n        - --port-metrics=9797\n        - --grpc-port=9999\n        - --grpc-service-name=podinfo\n        - --level=info\n        - --random-delay=false\n        - --random-error=false\n        env:\n        - name: PODINFO_UI_COLOR\n          value: '#34577c'\n        image: ghcr.io/stefanprodan/podinfo:6.0.3\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/healthz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        name: podinfod\n        ports:\n        - containerPort: 9898\n          name: http\n          protocol: TCP\n        - containerPort: 9797\n          name: http-metrics\n          protocol: TCP\n        - containerPort: 9999\n          name: grpc\n          protocol: TCP\n        readinessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/readyz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        resources:\n          limits:\n            cpu: 2000m\n            memory: 512Mi\n          requests:\n            cpu: 100m\n            memory: 64Mi\n---\napiVersion: autoscaling/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: default\nspec:\n  maxReplicas: 4\n  metrics:\n  - resource:\n      name: cpu\n      target:\n        averageUtilization: 99\n        type: Utilization\n    type: Resource\n  minReplicas: 2\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: podinfo\n---\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/var-substitution/cluster.json",
    "content": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"gnetId\": null,\n  \"graphTooltip\": 0,\n  \"iteration\": 1636369574387,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"decimals\": 0,\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"blue\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 100\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 24,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"last\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"value\"\n      },\n      \"pluginVersion\": \"7.5.5\",\n      \"targets\": [\n        {\n          \"exemplar\": true,\n          \"expr\": \"count(gotk_reconcile_condition{namespace=~\\\"$operator_namespace\\\",exported_namespace=~\\\"$namespace\\\",type=\\\"Ready\\\",status=\\\"True\\\",kind=~\\\"Kustomization|HelmRelease\\\"})\\n-\\nsum(gotk_reconcile_condition{namespace=~\\\"$operator_namespace\\\",exported_namespace=~\\\"$namespace\\\",type=\\\"Ready\\\",status=\\\"Deleted\\\",kind=~\\\"Kustomization|HelmRelease\\\"})\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"Cluster Reconcilers\",\n      \"type\": \"stat\"\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 15,\n      \"panels\": [],\n      \"title\": \"Status\",\n      \"type\": \"row\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"schemaVersion\": 27,\n  \"style\": \"light\",\n  \"tags\": [\n    \"flux\"\n  ],\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Flux Cluster Stats\",\n  \"uid\": \"flux-cluster\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/var-substitution/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    environment: ${cluster_env:=dev}\n    region: ${cluster_region}\n  name: podinfo\nspec:\n  minReadySeconds: 3\n  revisionHistoryLimit: 5\n  progressDeadlineSeconds: 60\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 0\n    type: RollingUpdate\n  selector:\n    matchLabels:\n      app: podinfo\n  template:\n    metadata:\n      annotations:\n        prometheus.io/scrape: \"true\"\n        prometheus.io/port: \"9797\"\n      labels:\n        app: podinfo\n    spec:\n      containers:\n      - name: podinfod\n        image: ghcr.io/stefanprodan/podinfo:6.0.10\n        imagePullPolicy: IfNotPresent\n        ports:\n        - name: http\n          containerPort: 9898\n          protocol: TCP\n        - name: http-metrics\n          containerPort: 9797\n          protocol: TCP\n        - name: grpc\n          containerPort: 9999\n          protocol: TCP\n        command:\n        - ./podinfo\n        - --port=9898\n        - --port-metrics=9797\n        - --grpc-port=9999\n        - --grpc-service-name=podinfo\n        - --level=info\n        - --random-delay=false\n        - --random-error=false\n        env:\n        - name: PODINFO_UI_COLOR\n          value: \"#34577c\"\n        livenessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/healthz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        readinessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/readyz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        resources:\n          limits:\n            cpu: 2000m\n            memory: 512Mi\n          requests:\n            cpu: 100m\n            memory: 64Mi\n"
  },
  {
    "path": "cmd/flux/testdata/build-kustomization/var-substitution/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n  - ./deployment.yaml\ngeneratorOptions:\n  labels:\n    kustomize.toolkit.fluxcd.io/substitute: disabled\nconfigMapGenerator:\n  - name: flux-grafana-dashboards\n    files:\n      - cluster.json\n"
  },
  {
    "path": "cmd/flux/testdata/check/check_pre.golden",
    "content": "► checking prerequisites\n✔ Kubernetes {{ .serverVersion }} >=1.33.0-0\n✔ prerequisites checks passed\n"
  },
  {
    "path": "cmd/flux/testdata/cluster_info/gitrepositories.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.12.0\n  name: gitrepositories.source.toolkit.fluxcd.io\nspec:\n  group: source.toolkit.fluxcd.io\n  names:\n    kind: GitRepository\n    listKind: GitRepositoryList\n    plural: gitrepositories\n    shortNames:\n    - gitrepo\n    singular: gitrepository\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.url\n      name: URL\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    - jsonPath: .status.conditions[?(@.type==\"Ready\")].status\n      name: Ready\n      type: string\n    - jsonPath: .status.conditions[?(@.type==\"Ready\")].message\n      name: Status\n      type: string\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: GitRepository is the Schema for the gitrepositories API.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: GitRepositorySpec specifies the required configuration to\n              produce an Artifact for a Git repository.\n            properties:\n              ignore:\n                description: Ignore overrides the set of excluded patterns in the\n                  .sourceignore format (which is the same as .gitignore). If not provided,\n                  a default will be used, consult the documentation for your version\n                  to find out what those are.\n                type: string\n              include:\n                description: Include specifies a list of GitRepository resources which\n                  Artifacts should be included in the Artifact produced for this GitRepository.\n                items:\n                  description: GitRepositoryInclude specifies a local reference to\n                    a GitRepository which Artifact (sub-)contents must be included,\n                    and where they should be placed.\n                  properties:\n                    fromPath:\n                      description: FromPath specifies the path to copy contents from,\n                        defaults to the root of the Artifact.\n                      type: string\n                    repository:\n                      description: GitRepositoryRef specifies the GitRepository which\n                        Artifact contents must be included.\n                      properties:\n                        name:\n                          description: Name of the referent.\n                          type: string\n                      required:\n                      - name\n                      type: object\n                    toPath:\n                      description: ToPath specifies the path to copy contents to,\n                        defaults to the name of the GitRepositoryRef.\n                      type: string\n                  required:\n                  - repository\n                  type: object\n                type: array\n              interval:\n                description: Interval at which the GitRepository URL is checked for\n                  updates. This interval is approximate and may be subject to jitter\n                  to ensure efficient use of resources.\n                pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$\n                type: string\n              proxySecretRef:\n                description: ProxySecretRef specifies the Secret containing the proxy\n                  configuration to use while communicating with the Git server.\n                properties:\n                  name:\n                    description: Name of the referent.\n                    type: string\n                required:\n                - name\n                type: object\n              recurseSubmodules:\n                description: RecurseSubmodules enables the initialization of all submodules\n                  within the GitRepository as cloned from the URL, using their default\n                  settings.\n                type: boolean\n              ref:\n                description: Reference specifies the Git reference to resolve and\n                  monitor for changes, defaults to the 'master' branch.\n                properties:\n                  branch:\n                    description: Branch to check out, defaults to 'master' if no other\n                      field is defined.\n                    type: string\n                  commit:\n                    description: \"Commit SHA to check out, takes precedence over all\n                      reference fields. \\n This can be combined with Branch to shallow\n                      clone the branch, in which the commit is expected to exist.\"\n                    type: string\n                  name:\n                    description: \"Name of the reference to check out; takes precedence\n                      over Branch, Tag and SemVer. \\n It must be a valid Git reference:\n                      https://git-scm.com/docs/git-check-ref-format#_description Examples:\n                      \\\"refs/heads/main\\\", \\\"refs/tags/v0.1.0\\\", \\\"refs/pull/420/head\\\",\n                      \\\"refs/merge-requests/1/head\\\"\"\n                    type: string\n                  semver:\n                    description: SemVer tag expression to check out, takes precedence\n                      over Tag.\n                    type: string\n                  tag:\n                    description: Tag to check out, takes precedence over Branch.\n                    type: string\n                type: object\n              secretRef:\n                description: SecretRef specifies the Secret containing authentication\n                  credentials for the GitRepository. For HTTPS repositories the Secret\n                  must contain 'username' and 'password' fields for basic auth or\n                  'bearerToken' field for token auth. For SSH repositories the Secret\n                  must contain 'identity' and 'known_hosts' fields.\n                properties:\n                  name:\n                    description: Name of the referent.\n                    type: string\n                required:\n                - name\n                type: object\n              suspend:\n                description: Suspend tells the controller to suspend the reconciliation\n                  of this GitRepository.\n                type: boolean\n              timeout:\n                default: 60s\n                description: Timeout for Git operations like cloning, defaults to\n                  60s.\n                pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m))+$\n                type: string\n              url:\n                description: URL specifies the Git repository URL, it can be an HTTP/S\n                  or SSH address.\n                pattern: ^(http|https|ssh)://.*$\n                type: string\n              verify:\n                description: Verification specifies the configuration to verify the\n                  Git commit signature(s).\n                properties:\n                  mode:\n                    default: HEAD\n                    description: \"Mode specifies which Git object(s) should be verified.\n                      \\n The variants \\\"head\\\" and \\\"HEAD\\\" both imply the same thing,\n                      i.e. verify the commit that the HEAD of the Git repository points\n                      to. The variant \\\"head\\\" solely exists to ensure backwards compatibility.\"\n                    enum:\n                    - head\n                    - HEAD\n                    - Tag\n                    - TagAndHEAD\n                    type: string\n                  secretRef:\n                    description: SecretRef specifies the Secret containing the public\n                      keys of trusted Git authors.\n                    properties:\n                      name:\n                        description: Name of the referent.\n                        type: string\n                    required:\n                    - name\n                    type: object\n                required:\n                - secretRef\n                type: object\n            required:\n            - interval\n            - url\n            type: object\n          status:\n            default:\n              observedGeneration: -1\n            description: GitRepositoryStatus records the observed state of a Git repository.\n            properties:\n              artifact:\n                description: Artifact represents the last successful GitRepository\n                  reconciliation.\n                properties:\n                  digest:\n                    description: Digest is the digest of the file in the form of '<algorithm>:<checksum>'.\n                    pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$\n                    type: string\n                  lastUpdateTime:\n                    description: LastUpdateTime is the timestamp corresponding to\n                      the last update of the Artifact.\n                    format: date-time\n                    type: string\n                  metadata:\n                    additionalProperties:\n                      type: string\n                    description: Metadata holds upstream information such as OCI annotations.\n                    type: object\n                  path:\n                    description: Path is the relative file path of the Artifact. It\n                      can be used to locate the file in the root of the Artifact storage\n                      on the local file system of the controller managing the Source.\n                    type: string\n                  revision:\n                    description: Revision is a human-readable identifier traceable\n                      in the origin source system. It can be a Git commit SHA, Git\n                      tag, a Helm chart version, etc.\n                    type: string\n                  size:\n                    description: Size is the number of bytes in the file.\n                    format: int64\n                    type: integer\n                  url:\n                    description: URL is the HTTP address of the Artifact as exposed\n                      by the controller managing the Source. It can be used to retrieve\n                      the Artifact for consumption, e.g. by another controller applying\n                      the Artifact contents.\n                    type: string\n                required:\n                - lastUpdateTime\n                - path\n                - revision\n                - url\n                type: object\n              conditions:\n                description: Conditions holds the conditions for the GitRepository.\n                items:\n                  description: \"Condition contains details for one aspect of the current\n                    state of this API Resource. --- This struct is intended for direct\n                    use as an array at the field path .status.conditions.  For example,\n                    \\n type FooStatus struct{ // Represents the observations of a\n                    foo's current state. // Known .status.conditions.type are: \\\"Available\\\",\n                    \\\"Progressing\\\", and \\\"Degraded\\\" // +patchMergeKey=type // +patchStrategy=merge\n                    // +listType=map // +listMapKey=type Conditions []metav1.Condition\n                    `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\"\n                    protobuf:\\\"bytes,1,rep,name=conditions\\\"` \\n // other fields }\"\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the condition\n                        transitioned from one status to another. This should be when\n                        the underlying condition changed.  If that is not known, then\n                        using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: message is a human readable message indicating\n                        details about the transition. This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: observedGeneration represents the .metadata.generation\n                        that the condition was set based upon. For instance, if .metadata.generation\n                        is currently 12, but the .status.conditions[x].observedGeneration\n                        is 9, the condition is out of date with respect to the current\n                        state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: reason contains a programmatic identifier indicating\n                        the reason for the condition's last transition. Producers\n                        of specific condition types may define expected values and\n                        meanings for this field, and whether the values are considered\n                        a guaranteed API. The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                        --- Many .condition.type values are consistent across resources\n                        like Available, but because arbitrary conditions can be useful\n                        (see .node.status.conditions), the ability to deconflict is\n                        important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n              includedArtifacts:\n                description: IncludedArtifacts contains a list of the last successfully\n                  included Artifacts as instructed by GitRepositorySpec.Include.\n                items:\n                  description: Artifact represents the output of a Source reconciliation.\n                  properties:\n                    digest:\n                      description: Digest is the digest of the file in the form of\n                        '<algorithm>:<checksum>'.\n                      pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$\n                      type: string\n                    lastUpdateTime:\n                      description: LastUpdateTime is the timestamp corresponding to\n                        the last update of the Artifact.\n                      format: date-time\n                      type: string\n                    metadata:\n                      additionalProperties:\n                        type: string\n                      description: Metadata holds upstream information such as OCI\n                        annotations.\n                      type: object\n                    path:\n                      description: Path is the relative file path of the Artifact.\n                        It can be used to locate the file in the root of the Artifact\n                        storage on the local file system of the controller managing\n                        the Source.\n                      type: string\n                    revision:\n                      description: Revision is a human-readable identifier traceable\n                        in the origin source system. It can be a Git commit SHA, Git\n                        tag, a Helm chart version, etc.\n                      type: string\n                    size:\n                      description: Size is the number of bytes in the file.\n                      format: int64\n                      type: integer\n                    url:\n                      description: URL is the HTTP address of the Artifact as exposed\n                        by the controller managing the Source. It can be used to retrieve\n                        the Artifact for consumption, e.g. by another controller applying\n                        the Artifact contents.\n                      type: string\n                  required:\n                  - lastUpdateTime\n                  - path\n                  - revision\n                  - url\n                  type: object\n                type: array\n              lastHandledReconcileAt:\n                description: LastHandledReconcileAt holds the value of the most recent\n                  reconcile request value, so a change of the annotation value can\n                  be detected.\n                type: string\n              observedGeneration:\n                description: ObservedGeneration is the last observed generation of\n                  the GitRepository object.\n                format: int64\n                type: integer\n              observedIgnore:\n                description: ObservedIgnore is the observed exclusion patterns used\n                  for constructing the source artifact.\n                type: string\n              observedInclude:\n                description: ObservedInclude is the observed list of GitRepository\n                  resources used to produce the current Artifact.\n                items:\n                  description: GitRepositoryInclude specifies a local reference to\n                    a GitRepository which Artifact (sub-)contents must be included,\n                    and where they should be placed.\n                  properties:\n                    fromPath:\n                      description: FromPath specifies the path to copy contents from,\n                        defaults to the root of the Artifact.\n                      type: string\n                    repository:\n                      description: GitRepositoryRef specifies the GitRepository which\n                        Artifact contents must be included.\n                      properties:\n                        name:\n                          description: Name of the referent.\n                          type: string\n                      required:\n                      - name\n                      type: object\n                    toPath:\n                      description: ToPath specifies the path to copy contents to,\n                        defaults to the name of the GitRepositoryRef.\n                      type: string\n                  required:\n                  - repository\n                  type: object\n                type: array\n              observedRecurseSubmodules:\n                description: ObservedRecurseSubmodules is the observed resource submodules\n                  configuration used to produce the current Artifact.\n                type: boolean\n              sourceVerificationMode:\n                description: SourceVerificationMode is the last used verification\n                  mode indicating which Git object(s) have been verified.\n                type: string\n            type: object\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "cmd/flux/testdata/create_hr/basic.yaml",
    "content": "---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  chart:\n    spec:\n      chart: podinfo\n      reconcileStrategy: ChartVersion\n      sourceRef:\n        kind: HelmRepository\n        name: podinfo\n  interval: 1m0s\n"
  },
  {
    "path": "cmd/flux/testdata/create_hr/hc_basic.yaml",
    "content": "---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  chartRef:\n    kind: HelmChart\n    name: podinfo\n  interval: 1m0s\n"
  },
  {
    "path": "cmd/flux/testdata/create_hr/or_basic.yaml",
    "content": "---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  chartRef:\n    kind: OCIRepository\n    name: podinfo\n  interval: 1m0s\n"
  },
  {
    "path": "cmd/flux/testdata/create_hr/setup-source.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmRepository\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  interval: 1m0s\n  provider: generic\n  type: oci\n  url: oci://ghcr.io/stefanprodan/charts\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmChart\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  interval: 1m0s\n  chart: podinfo\n  sourceRef:\n    kind: HelmRepository\n    name: podinfo\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: OCIRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 10m\n  url: oci://ghcr.io/stefanprodan/manifests/podinfo\n  ref:\n    tag: latest\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/git/ecdsa-password.private",
    "content": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABA9i7hZ7m\nUBPxF7GuUswZiXAAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz\ndHAyNTYAAABBBOOAQfhwylg6WhJRXha2K1dJY5BG12nsS7YyFlKPAovOAC8d8rTOzjjDWu\nkG24JVRrXoWUN6eWWvxDvMd5pZT1wAAADAepYZPGTQnH6genhU+y9aD1GFO8BsODIllxEq\nT8n2eLAmPWYLv4HhgtmbqtsOmTE9sVM5ynYj/dAX1SreoGeoMEVFIt1cYtWgyuJccEiK4t\n1JQLFLMKnBRTY+yIdD6gX7tLRRL/jqzMR4XZF5/Yf48lvU4h+ljuOitWH3ea9142izl2Wk\neXAfeoezJaDntZUUEYvKMI5U6iWVni+c7vOcJFTZgBeV4i54ua06tY5mE/mWARldiSDtJG\nyGEOankDnf\n-----END OPENSSH PRIVATE KEY-----\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/git/ecdsa.private",
    "content": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTuiu+gGwLIu9E5J4vRshoIBHScKx4Y\nf9oniWMsLFHXq5p9GJ/eb9Cr3jgNACnGOIGOqlwBQvP5rCJuaJ0pCRKUAAAAuNE1GtvRNR\nrbAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBO6K76AbAsi70Tkn\ni9GyGggEdJwrHhh/2ieJYywsUdermn0Yn95v0KveOA0AKcY4gY6qXAFC8/msIm5onSkJEp\nQAAAAhAPaO6PiN+1238KMrHg34M7XdftGypt2/UKEz2L2Pf40yAAAAH3NvbXRvY2hpb255\nZWt3ZXJlQFNvbXRvY2hpcy1NQlA=\n-----END OPENSSH PRIVATE KEY-----\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/git/git-bearer-token.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: bearer-token-auth\n  namespace: my-namespace\nstringData:\n  bearerToken: ghp_baR2qnFF0O41WlucePL3udt2N9vVZS4R0hAS\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/git/git-ssh-secret-password.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: podinfo-auth\n  namespace: my-namespace\nstringData:\n  identity: |\n    -----BEGIN OPENSSH PRIVATE KEY-----\n    b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABA9i7hZ7m\n    UBPxF7GuUswZiXAAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz\n    dHAyNTYAAABBBOOAQfhwylg6WhJRXha2K1dJY5BG12nsS7YyFlKPAovOAC8d8rTOzjjDWu\n    kG24JVRrXoWUN6eWWvxDvMd5pZT1wAAADAepYZPGTQnH6genhU+y9aD1GFO8BsODIllxEq\n    T8n2eLAmPWYLv4HhgtmbqtsOmTE9sVM5ynYj/dAX1SreoGeoMEVFIt1cYtWgyuJccEiK4t\n    1JQLFLMKnBRTY+yIdD6gX7tLRRL/jqzMR4XZF5/Yf48lvU4h+ljuOitWH3ea9142izl2Wk\n    eXAfeoezJaDntZUUEYvKMI5U6iWVni+c7vOcJFTZgBeV4i54ua06tY5mE/mWARldiSDtJG\n    yGEOankDnf\n    -----END OPENSSH PRIVATE KEY-----\n  identity.pub: |\n    ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOOAQfhwylg6WhJRXha2K1dJY5BG12nsS7YyFlKPAovOAC8d8rTOzjjDWukG24JVRrXoWUN6eWWvxDvMd5pZT1w=\n  known_hosts: github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\n  password: password\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/git/git-ssh-secret.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: podinfo-auth\n  namespace: my-namespace\nstringData:\n  identity: |\n    -----BEGIN OPENSSH PRIVATE KEY-----\n    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n    1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTuiu+gGwLIu9E5J4vRshoIBHScKx4Y\n    f9oniWMsLFHXq5p9GJ/eb9Cr3jgNACnGOIGOqlwBQvP5rCJuaJ0pCRKUAAAAuNE1GtvRNR\n    rbAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBO6K76AbAsi70Tkn\n    i9GyGggEdJwrHhh/2ieJYywsUdermn0Yn95v0KveOA0AKcY4gY6qXAFC8/msIm5onSkJEp\n    QAAAAhAPaO6PiN+1238KMrHg34M7XdftGypt2/UKEz2L2Pf40yAAAAH3NvbXRvY2hpb255\n    ZWt3ZXJlQFNvbXRvY2hpcy1NQlA=\n    -----END OPENSSH PRIVATE KEY-----\n  identity.pub: |\n    ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBO6K76AbAsi70Tkni9GyGggEdJwrHhh/2ieJYywsUdermn0Yn95v0KveOA0AKcY4gY6qXAFC8/msIm5onSkJEpQ=\n  known_hosts: github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/git/secret-ca-crt.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: ca-crt\n  namespace: my-namespace\nstringData:\n  ca.crt: ca-data\n  password: my-password\n  username: my-username\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/git/secret-git-basic.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: podinfo-auth\n  namespace: my-namespace\nstringData:\n  password: my-password\n  username: my-username\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/githubapp/secret-with-baseurl.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: appinfo\n  namespace: my-namespace\nstringData:\n  githubAppBaseURL: www.example.com/api/v3\n  githubAppID: \"1\"\n  githubAppInstallationID: \"2\"\n  githubAppPrivateKey: |-\n    -----BEGIN RSA PRIVATE KEY-----\n    YcE2CgWILk+uiVNseHnOU2frG7k2RJZtdDo8GNI6pQWFlwU/NsQoJBrtEDyYVkap\n    PLv7VoJ2pr6l5IEwH++naL2McuCcwhW/CeaVb/IuSCFFMwb40zRrlqp6IB5VkMhm\n    zSde2KD/ilB1YUhaAv0qaBHLTGBvVgxut1eIyvqVC+ArWoqJ7rQTst+Arp3UAiIm\n    LsXqL2iZvVvCH0FiDwBIfxAMhl6fnPzuQsZBiRLPdD67jubPseN1P5JBRw3WTton\n    Fa2RLLByuyge7bWh2o1hjEx2w2ZpIhosQRyDs1sPXP5TI92RzOcx1CZaTZ6V5E+T\n    MROFeZmxBHYon1Y4Rw+jCSXovNyHbMBpMI67nwIDAQABAoIBAC4UrkusU8r7ilFu\n    w1LWDm09+8WSIk9KgYMoBceAqH+b9DU7hrMFrKkO/cr0Mijr1somv6B3MG83WUcB\n    FkhEBrwXKnh499iiO/SUo+7kaq0WLQ7mQ2Q9wpMmkkjnr0tgydAno/uNNITSaqmk\n    YcE2CgWILk+uiVNseHnOU2frG7k2RJZtdDo8GNI6pQWFlwU/NsQoJBrtEDyYVkap\n    Fa2RLLByuyge7bWh2o1hjEx2w2ZpIhosQRyDs1sPXP5TI92RzOcx1CZaTZ6V5E+T\n    zSde2KD/ilB1YUhaAv0qaBHLTGBvVgxut1eIyvqVC+ArWoqJ7rQTst+Arp3UAiIm\n    ihlXNkECgYEA3abZJZuVarHPlAqRYkprs0O+DrP6sPlmVQp+nq8y3Qg00U+N7AuP\n    Y1riLo3gWq7LajkGTygWLmru2mhWsETxt+R4BtnREUq8kDEoCfEwPlHfqfphvBZL\n    j5eL60QTKAqSOVqMgIzqJyxa5FGgPGqWpLDLopyVeoyNdZwcuCQzFgkCgYEA25dm\n    PLv7VoJ2pr6l5IEwH++naL2McuCcwhW/CeaVb/IuSCFFMwb40zRrlqp6IB5VkMhm\n    MkvaCGIAH+lfJrtTSujFaOIGFy+0ZwP+LNqHUKih14y8Qv9dEP0kaXkAD3fO3Y97\n    Nj+Q2c06JpojgBKBMwVvT7M53w9KEoNKpoKBbmcCgYBelHyiRJJsdbVKyXuiAnmU\n    g/qMkZYOgE1/SjwfgEjm8kJ/cj/wEjq8PaK4FMhAScf46p5blpJoei6zucQL8U9n\n    lbD102oXw9lUefVI0McyQIN9J58ewDC79AG7gU/fTSt6F75OeFLOJmoedQo33Y+s\n    dNhf6gsKwQD3x4aluKSn6QKBgD8HbvBAKV6P4vIiFzS0QvWtpeKam2EDHI+h+WsP\n    nD77QoG/EPvpjJS9/KWgZRPz6U+0M5V0y73MZVzkbbVT/uwfgF2G91lXAr4Kfuh5\n    w1LWDm09+8WSIk9KgYMoBceAqH+b9DU7hrMFrKkO/cr0Mijr1somv6B3MG83WUcB\n    qCEDAoGACl8ClvMJR2uNWdaWnCz9tyPdHYgEusJ0OIP+WUY2ToYQWSlA0zNpc21Y\n    lbD102oXw9lUefVI0McyQIN9J58ewDC79AG7gU/fTSt6F75OeFLOJmoedQo33Y+s\n    bUytJtOhHbLRNxwgalhjBUNWICrDktqJmumNOEOOPBqVz7RGwUg=\n    -----END RSA PRIVATE KEY-----\ntype: Opaque\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/githubapp/secret.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: appinfo\n  namespace: my-namespace\nstringData:\n  githubAppID: \"1\"\n  githubAppInstallationOwner: my-org\n  githubAppPrivateKey: |-\n    -----BEGIN RSA PRIVATE KEY-----\n    YcE2CgWILk+uiVNseHnOU2frG7k2RJZtdDo8GNI6pQWFlwU/NsQoJBrtEDyYVkap\n    PLv7VoJ2pr6l5IEwH++naL2McuCcwhW/CeaVb/IuSCFFMwb40zRrlqp6IB5VkMhm\n    zSde2KD/ilB1YUhaAv0qaBHLTGBvVgxut1eIyvqVC+ArWoqJ7rQTst+Arp3UAiIm\n    LsXqL2iZvVvCH0FiDwBIfxAMhl6fnPzuQsZBiRLPdD67jubPseN1P5JBRw3WTton\n    Fa2RLLByuyge7bWh2o1hjEx2w2ZpIhosQRyDs1sPXP5TI92RzOcx1CZaTZ6V5E+T\n    MROFeZmxBHYon1Y4Rw+jCSXovNyHbMBpMI67nwIDAQABAoIBAC4UrkusU8r7ilFu\n    w1LWDm09+8WSIk9KgYMoBceAqH+b9DU7hrMFrKkO/cr0Mijr1somv6B3MG83WUcB\n    FkhEBrwXKnh499iiO/SUo+7kaq0WLQ7mQ2Q9wpMmkkjnr0tgydAno/uNNITSaqmk\n    YcE2CgWILk+uiVNseHnOU2frG7k2RJZtdDo8GNI6pQWFlwU/NsQoJBrtEDyYVkap\n    Fa2RLLByuyge7bWh2o1hjEx2w2ZpIhosQRyDs1sPXP5TI92RzOcx1CZaTZ6V5E+T\n    zSde2KD/ilB1YUhaAv0qaBHLTGBvVgxut1eIyvqVC+ArWoqJ7rQTst+Arp3UAiIm\n    ihlXNkECgYEA3abZJZuVarHPlAqRYkprs0O+DrP6sPlmVQp+nq8y3Qg00U+N7AuP\n    Y1riLo3gWq7LajkGTygWLmru2mhWsETxt+R4BtnREUq8kDEoCfEwPlHfqfphvBZL\n    j5eL60QTKAqSOVqMgIzqJyxa5FGgPGqWpLDLopyVeoyNdZwcuCQzFgkCgYEA25dm\n    PLv7VoJ2pr6l5IEwH++naL2McuCcwhW/CeaVb/IuSCFFMwb40zRrlqp6IB5VkMhm\n    MkvaCGIAH+lfJrtTSujFaOIGFy+0ZwP+LNqHUKih14y8Qv9dEP0kaXkAD3fO3Y97\n    Nj+Q2c06JpojgBKBMwVvT7M53w9KEoNKpoKBbmcCgYBelHyiRJJsdbVKyXuiAnmU\n    g/qMkZYOgE1/SjwfgEjm8kJ/cj/wEjq8PaK4FMhAScf46p5blpJoei6zucQL8U9n\n    lbD102oXw9lUefVI0McyQIN9J58ewDC79AG7gU/fTSt6F75OeFLOJmoedQo33Y+s\n    dNhf6gsKwQD3x4aluKSn6QKBgD8HbvBAKV6P4vIiFzS0QvWtpeKam2EDHI+h+WsP\n    nD77QoG/EPvpjJS9/KWgZRPz6U+0M5V0y73MZVzkbbVT/uwfgF2G91lXAr4Kfuh5\n    w1LWDm09+8WSIk9KgYMoBceAqH+b9DU7hrMFrKkO/cr0Mijr1somv6B3MG83WUcB\n    qCEDAoGACl8ClvMJR2uNWdaWnCz9tyPdHYgEusJ0OIP+WUY2ToYQWSlA0zNpc21Y\n    lbD102oXw9lUefVI0McyQIN9J58ewDC79AG7gU/fTSt6F75OeFLOJmoedQo33Y+s\n    bUytJtOhHbLRNxwgalhjBUNWICrDktqJmumNOEOOPBqVz7RGwUg=\n    -----END RSA PRIVATE KEY-----\ntype: Opaque\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/githubapp/test-private-key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nYcE2CgWILk+uiVNseHnOU2frG7k2RJZtdDo8GNI6pQWFlwU/NsQoJBrtEDyYVkap\nPLv7VoJ2pr6l5IEwH++naL2McuCcwhW/CeaVb/IuSCFFMwb40zRrlqp6IB5VkMhm\nzSde2KD/ilB1YUhaAv0qaBHLTGBvVgxut1eIyvqVC+ArWoqJ7rQTst+Arp3UAiIm\nLsXqL2iZvVvCH0FiDwBIfxAMhl6fnPzuQsZBiRLPdD67jubPseN1P5JBRw3WTton\nFa2RLLByuyge7bWh2o1hjEx2w2ZpIhosQRyDs1sPXP5TI92RzOcx1CZaTZ6V5E+T\nMROFeZmxBHYon1Y4Rw+jCSXovNyHbMBpMI67nwIDAQABAoIBAC4UrkusU8r7ilFu\nw1LWDm09+8WSIk9KgYMoBceAqH+b9DU7hrMFrKkO/cr0Mijr1somv6B3MG83WUcB\nFkhEBrwXKnh499iiO/SUo+7kaq0WLQ7mQ2Q9wpMmkkjnr0tgydAno/uNNITSaqmk\nYcE2CgWILk+uiVNseHnOU2frG7k2RJZtdDo8GNI6pQWFlwU/NsQoJBrtEDyYVkap\nFa2RLLByuyge7bWh2o1hjEx2w2ZpIhosQRyDs1sPXP5TI92RzOcx1CZaTZ6V5E+T\nzSde2KD/ilB1YUhaAv0qaBHLTGBvVgxut1eIyvqVC+ArWoqJ7rQTst+Arp3UAiIm\nihlXNkECgYEA3abZJZuVarHPlAqRYkprs0O+DrP6sPlmVQp+nq8y3Qg00U+N7AuP\nY1riLo3gWq7LajkGTygWLmru2mhWsETxt+R4BtnREUq8kDEoCfEwPlHfqfphvBZL\nj5eL60QTKAqSOVqMgIzqJyxa5FGgPGqWpLDLopyVeoyNdZwcuCQzFgkCgYEA25dm\nPLv7VoJ2pr6l5IEwH++naL2McuCcwhW/CeaVb/IuSCFFMwb40zRrlqp6IB5VkMhm\nMkvaCGIAH+lfJrtTSujFaOIGFy+0ZwP+LNqHUKih14y8Qv9dEP0kaXkAD3fO3Y97\nNj+Q2c06JpojgBKBMwVvT7M53w9KEoNKpoKBbmcCgYBelHyiRJJsdbVKyXuiAnmU\ng/qMkZYOgE1/SjwfgEjm8kJ/cj/wEjq8PaK4FMhAScf46p5blpJoei6zucQL8U9n\nlbD102oXw9lUefVI0McyQIN9J58ewDC79AG7gU/fTSt6F75OeFLOJmoedQo33Y+s\ndNhf6gsKwQD3x4aluKSn6QKBgD8HbvBAKV6P4vIiFzS0QvWtpeKam2EDHI+h+WsP\nnD77QoG/EPvpjJS9/KWgZRPz6U+0M5V0y73MZVzkbbVT/uwfgF2G91lXAr4Kfuh5\nw1LWDm09+8WSIk9KgYMoBceAqH+b9DU7hrMFrKkO/cr0Mijr1somv6B3MG83WUcB\nqCEDAoGACl8ClvMJR2uNWdaWnCz9tyPdHYgEusJ0OIP+WUY2ToYQWSlA0zNpc21Y\nlbD102oXw9lUefVI0McyQIN9J58ewDC79AG7gU/fTSt6F75OeFLOJmoedQo33Y+s\nbUytJtOhHbLRNxwgalhjBUNWICrDktqJmumNOEOOPBqVz7RGwUg=\n-----END RSA PRIVATE KEY-----"
  },
  {
    "path": "cmd/flux/testdata/create_secret/helm/secret-helm.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: helm-secret\n  namespace: my-namespace\nstringData:\n  password: my-password\n  username: my-username\ntype: kubernetes.io/basic-auth\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/notation/invalid-trust-policy.json",
    "content": "{\n    \"version\": \"1.0\",\n    \"trustPolicies\": [{}]\n}\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/notation/invalid.json",
    "content": "\"\"\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/notation/secret-ca-crt.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: notation-config\n  namespace: my-namespace\nstringData:\n  ca.crt: ca-data-crt\n  trustpolicy.json: |\n    {\n        \"version\": \"1.0\",\n        \"trustPolicies\": [\n            {\n                \"name\": \"fluxcd.io\",\n                \"registryScopes\": [\n                    \"*\"\n                ],\n                \"signatureVerification\": {\n                    \"level\" : \"strict\"\n                },\n                \"trustStores\": [ \"ca:fluxcd.io\" ],\n                \"trustedIdentities\": [\n                    \"*\"\n                ]\n            }\n        ]\n    }\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/notation/secret-ca-multi.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: notation-config\n  namespace: my-namespace\nstringData:\n  ca.crt: ca-data-crt\n  ca.pem: ca-data-pem\n  trustpolicy.json: |\n    {\n        \"version\": \"1.0\",\n        \"trustPolicies\": [\n            {\n                \"name\": \"fluxcd.io\",\n                \"registryScopes\": [\n                    \"*\"\n                ],\n                \"signatureVerification\": {\n                    \"level\" : \"strict\"\n                },\n                \"trustStores\": [ \"ca:fluxcd.io\" ],\n                \"trustedIdentities\": [\n                    \"*\"\n                ]\n            }\n        ]\n    }\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/notation/secret-ca-pem.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: notation-config\n  namespace: my-namespace\nstringData:\n  ca.pem: ca-data-pem\n  trustpolicy.json: |\n    {\n        \"version\": \"1.0\",\n        \"trustPolicies\": [\n            {\n                \"name\": \"fluxcd.io\",\n                \"registryScopes\": [\n                    \"*\"\n                ],\n                \"signatureVerification\": {\n                    \"level\" : \"strict\"\n                },\n                \"trustStores\": [ \"ca:fluxcd.io\" ],\n                \"trustedIdentities\": [\n                    \"*\"\n                ]\n            }\n        ]\n    }\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/notation/test-ca.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDbDCCAlSgAwIBAgIUP7zhmTw5XTWLcgBGkBEsErMOkz4wDQYJKoZIhvcNAQEL\nBQAwWjELMAkGA1UEBhMCUk8xCzAJBgNVBAgMAkJVMRIwEAYDVQQHDAlCdWNoYXJl\nc3QxDzANBgNVBAoMBk5vdGFyeTEZMBcGA1UEAwwQc3RlZmFucHJvZGFuLmNvbTAe\nFw0yNDAyMjUxMDAyMzZaFw0yOTAyMjQxMDAyMzZaMFoxCzAJBgNVBAYTAlJPMQsw\nCQYDVQQIDAJCVTESMBAGA1UEBwwJQnVjaGFyZXN0MQ8wDQYDVQQKDAZOb3Rhcnkx\nGTAXBgNVBAMMEHN0ZWZhbnByb2Rhbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQDtH4oPi3SyX/DGv6NdjIvmApvD9eeSgsmHdwpAly8T9D2me+fx\nZ+wRNJmq4aq/A1anX+Sg28iwHzV+1WKpsHnjYzDAJSEYP2S8A5H1nGRKUoibdijw\nC3QBh5C75rjF/tmZVSX/Vgbf3HJJEsF4WUxWabLxoV2QLo7UlEsQd9+bSeKNMncx\n1+E6FdbRCrYo90iobvZJ8K/S2zCWq/JTeHfTnmSEDhx6nMJcaSjvMPn3zyauWcQw\ndDpkcaGiJ64fEJRT2OFxXv9u+vDmIMKzo/Wjbd+IzFj6YY4VisK88aU7tmDelnk5\ngQB9eu62PFoaVsYJp4VOhblFKvGJpQwbWB9BAgMBAAGjKjAoMA4GA1UdDwEB/wQE\nAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEA\n6x+C6hAIbLwMvkNx4K5p7Qe/pLQR0VwQFAw10yr/5KSN+YKFpon6pQ0TebL7qll+\nuBGZvtQhN6v+DlnVqB7lvJKd+89isgirkkews5KwuXg7Gv5UPIugH0dXISZU8DMJ\n7J4oKREv5HzdFmfsUfNlQcfyVTjKL6UINXfKGdqNNxXxR9b4a1TY2JcmEhzBTHaq\nZqX6HK784a0dB7aHgeFrFwPCCP4M684Hs7CFbk3jo2Ef4ljnB5AyWpe8pwCLMdRt\nUjSjL5xJWVQvRU+STQsPr6SvpokPCG4rLQyjgeYYk4CCj5piSxbSUZFavq8v1y7Y\nm91USVqfeUX7ZzjDxPHE2A==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/notation/test-ca2.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDbDCCAlSgAwIBAgIUP7zhmTw5XTWLcgBGkBEsErMOkz4wDQYJKoZIhvcNAQEL\nBQAwWjELMAkGA1UEBhMCUk8xCzAJBgNVBAgMAkJVMRIwEAYDVQQHDAlCdWNoYXJl\nc3QxDzANBgNVBAoMBk5vdGFyeTEZMBcGA1UEAwwQc3RlZmFucHJvZGFuLmNvbTAe\nFw0yNDAyMjUxMDAyMzZaFw0yOTAyMjQxMDAyMzZaMFoxCzAJBgNVBAYTAlJPMQsw\nCQYDVQQIDAJCVTESMBAGA1UEBwwJQnVjaGFyZXN0MQ8wDQYDVQQKDAZOb3Rhcnkx\nGTAXBgNVBAMMEHN0ZWZhbnByb2Rhbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQDtH4oPi3SyX/DGv6NdjIvmApvD9eeSgsmHdwpAly8T9D2me+fx\nZ+wRNJmq4aq/A1anX+Sg28iwHzV+1WKpsHnjYzDAJSEYP2S8A5H1nGRKUoibdijw\nC3QBh5C75rjF/tmZVSX/Vgbf3HJJEsF4WUxWabLxoV2QLo7UlEsQd9+bSeKNMncx\n1+E6FdbRCrYo90iobvZJ8K/S2zCWq/JTeHfTnmSEDhx6nMJcaSjvMPn3zyauWcQw\ndDpkcaGiJ64fEJRT2OFxXv9u+vDmIMKzo/Wjbd+IzFj6YY4VisK88aU7tmDelnk5\ngQB9eu62PFoaVsYJp4VOhblFKvGJpQwbWB9BAgMBAAGjKjAoMA4GA1UdDwEB/wQE\nAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEA\n6x+C6hAIbLwMvkNx4K5p7Qe/pLQR0VwQFAw10yr/5KSN+YKFpon6pQ0TebL7qll+\nuBGZvtQhN6v+DlnVqB7lvJKd+89isgirkkews5KwuXg7Gv5UPIugH0dXISZU8DMJ\n7J4oKREv5HzdFmfsUfNlQcfyVTjKL6UINXfKGdqNNxXxR9b4a1TY2JcmEhzBTHaq\nZqX6HK784a0dB7aHgeFrFwPCCP4M684Hs7CFbk3jo2Ef4ljnB5AyWpe8pwCLMdRt\nUjSjL5xJWVQvRU+STQsPr6SvpokPCG4rLQyjgeYYk4CCj5piSxbSUZFavq8v1y7Y\nm91USVqfeUX7ZzjDxPHE2A==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/notation/test-trust-policy.json",
    "content": "{\n    \"version\": \"1.0\",\n    \"trustPolicies\": [\n        {\n            \"name\": \"fluxcd.io\",\n            \"registryScopes\": [\n                \"*\"\n            ],\n            \"signatureVerification\": {\n                \"level\" : \"strict\"\n            },\n            \"trustStores\": [ \"ca:fluxcd.io\" ],\n            \"trustedIdentities\": [\n                \"*\"\n            ]\n        }\n    ]\n}\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/oci/create-secret.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: ghcr\n  namespace: my-namespace\nstringData:\n  .dockerconfigjson: |-\n    {\n      \"auths\": {\n        \"ghcr.io\": {\n          \"username\": \"stefanprodan\",\n          \"password\": \"password\",\n          \"auth\": \"c3RlZmFucHJvZGFuOnBhc3N3b3Jk\"\n        }\n      }\n    }\ntype: kubernetes.io/dockerconfigjson\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/proxy/secret-proxy.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: proxy-secret\n  namespace: my-namespace\nstringData:\n  address: https://my-proxy.com\n  password: my-password\n  username: my-username\ntype: Opaque\n\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/tls/secret-tls.yaml",
    "content": "---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: certs\n  namespace: my-namespace\nstringData:\n  ca.crt: |\n    -----BEGIN CERTIFICATE-----\n    MIIBhzCCAS2gAwIBAgIUdsAtiX3gN0uk7ddxASWYE/tdv0wwCgYIKoZIzj0EAwIw\n    GTEXMBUGA1UEAxMOZXhhbXBsZS5jb20gQ0EwHhcNMjAwNDE3MDgxODAwWhcNMjUw\n    NDE2MDgxODAwWjAZMRcwFQYDVQQDEw5leGFtcGxlLmNvbSBDQTBZMBMGByqGSM49\n    AgEGCCqGSM49AwEHA0IABK7h/5D8bV93MmEdhu02JsS6ugB8s6PzRl3PV4xs3Sbr\n    RNkkM59+x3b0iWx/i76qPYpNLoiVUVXQmA9Y+4DbMxijUzBRMA4GA1UdDwEB/wQE\n    AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQGyUiU1QEZiMAqjsnIYTwZ\n    4yp5wzAPBgNVHREECDAGhwR/AAABMAoGCCqGSM49BAMCA0gAMEUCIQDzdtvKdE8O\n    1+WRTZ9MuSiFYcrEz7Zne7VXouDEKqKEigIgM4WlbDeuNCKbqhqj+xZV0pa3rweb\n    OD8EjjCMY69RMO0=\n    -----END CERTIFICATE-----\n  tls.crt: |\n    -----BEGIN CERTIFICATE-----\n    MIIFazCCA1OgAwIBAgIUT84jeO/ncOrqI+FY05Fzbg8Ed7MwDQYJKoZIhvcNAQEL\n    BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\n    GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTA4MDgxNDQyMzVaFw0yMjA4\n    MDgxNDQyMzVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\n    HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB\n    AQUAA4ICDwAwggIKAoICAQDn/rPsZ74oypiwCzLlx57zplTiCi/WLSF+MmLGuTvM\n    EQnV+OND2zFgvDIV/vFs3brkd6rLVI4NcdgSj4YKULCMwwOl45hQPdCTEPJvUhCm\n    M+FuQ0czmEEJSjZtdLFz1B7QB/JemNnbfigxM9mlg58AlBhVJqn8q64wd/kC/W/K\n    JTLJuBiVf12ZiPoPfO4WSxAqD3opZ8gdbmK0KYQAhKjEto6ZrYGisfwU1gt3l8M7\n    sCJSpEkOkpuQgJ8D+xzJS36VXBJQMMP9nAPps+x/rGFplsPMsXEFFiwvR1+FJZwz\n    lg2sJ91bLGZQ7vn74MfsGrxpiJwllRThJyT7C9V0sjb5trT2lEqZlP2dRSJYt7aJ\n    1crEcdGSl6RIKgxSV6Hk8dh/ZaTjrTwaKxVkPo2IeEXy5xrR7DyonOQ6Yes0KOCm\n    JB5yHkFlIVEnLm/HZXEtm3bPHsFgTZuInyBCOMXpUESuVZIw8YK+Vd6AExGPPwZ4\n    n5I/sCDxWII9owIj3LeLzdUG6JoroahhGmo8rgpbJpPnS+VgryQ/raUQjqDzDCuE\n    9vKXKBlSUqK6H9A+NMc0mme7M8/GX7T7ewFGUB/xsdrcO4yXjqHnAe0yLf8epDjC\n    hh76bYqwwinVrmfcNcRxFVJZW2z0gGdgkOkOLaVVb9ggPV2SNAHbN4A+St/iRYR5\n    awIDAQABo1MwUTAdBgNVHQ4EFgQUzMaCqVM30EZFfTeNUIJ5fNPAhaQwHwYDVR0j\n    BBgwFoAUzMaCqVM30EZFfTeNUIJ5fNPAhaQwDwYDVR0TAQH/BAUwAwEB/zANBgkq\n    hkiG9w0BAQsFAAOCAgEAVmk1rXtVkYR1Vs2Va/xrUaGXlFznhPU/Fft44kiEkkLp\n    mLVelWyAqvXYioqssZwuZnTjGz0DQPqzJjqwuGy4CHwPLmhCtfHplrbWo8a0ivYC\n    cL20KfZsG941siUh7LGBjTsq6mWBf2ytlFmg/fg93SgmqcEUAUcdps0JpZD8lgWB\n    ZMstfr6E3jaEus3OsvDD6hJNYZ5clJ5+ynLoWZ99A9JC0U46hmIZpRjbdSvasKpD\n    XrXTdpzyL/Do3znXE/yfoHv4//Rj2CpPHJLYRCIzvuf1mo1fWd53FjHvrbUvaHFz\n    CGuZROd4dC4Rx5nZw2ogIYvJ8m6HpIDkL3pBNSQJtIsvAYEQcotJoa5D/e9fu2Wr\n    +og37oCY4OXzViEBQvyxKD4cajNco1fgGKEaFROADwr3JceGI7Anq5W+xdUvAGNM\n    QuGeCueqNyrJ0CbQ1zEhwgpk/VYfB0u9m0bjMellRlKMdojby+FDCJtAJesx9no4\n    SQXyx+aNHhj3qReysjGNwZvBk1IHL04HAT+ogNiYhTl1J/YON4MB5UN6Y2PxP6uG\n    KvJGPigx4fAwfR/d78o5ngwoH9m+8FUg8+qllJ8XgIbl/VXKTk3G4ceOm4eBmrel\n    DwWuBhELSjtXWPWhMlkiebgejDbAear53Lia2Cc43zx/KuhMHBTlKY/vY4F2YiI=\n    -----END CERTIFICATE-----\n  tls.key: |\n    -----BEGIN PRIVATE KEY-----\n    MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDn/rPsZ74oypiw\n    CzLlx57zplTiCi/WLSF+MmLGuTvMEQnV+OND2zFgvDIV/vFs3brkd6rLVI4NcdgS\n    j4YKULCMwwOl45hQPdCTEPJvUhCmM+FuQ0czmEEJSjZtdLFz1B7QB/JemNnbfigx\n    M9mlg58AlBhVJqn8q64wd/kC/W/KJTLJuBiVf12ZiPoPfO4WSxAqD3opZ8gdbmK0\n    KYQAhKjEto6ZrYGisfwU1gt3l8M7sCJSpEkOkpuQgJ8D+xzJS36VXBJQMMP9nAPp\n    s+x/rGFplsPMsXEFFiwvR1+FJZwzlg2sJ91bLGZQ7vn74MfsGrxpiJwllRThJyT7\n    C9V0sjb5trT2lEqZlP2dRSJYt7aJ1crEcdGSl6RIKgxSV6Hk8dh/ZaTjrTwaKxVk\n    Po2IeEXy5xrR7DyonOQ6Yes0KOCmJB5yHkFlIVEnLm/HZXEtm3bPHsFgTZuInyBC\n    OMXpUESuVZIw8YK+Vd6AExGPPwZ4n5I/sCDxWII9owIj3LeLzdUG6JoroahhGmo8\n    rgpbJpPnS+VgryQ/raUQjqDzDCuE9vKXKBlSUqK6H9A+NMc0mme7M8/GX7T7ewFG\n    UB/xsdrcO4yXjqHnAe0yLf8epDjChh76bYqwwinVrmfcNcRxFVJZW2z0gGdgkOkO\n    LaVVb9ggPV2SNAHbN4A+St/iRYR5awIDAQABAoICAQCTxuixQ/wbW8IbEWcgeyHD\n    LkaPndGO6jyVeF73GvL+MDRFuj558NvpNLfqzvTWVf9AnQGMd5Xs9oGegRHu7Csp\n    3ucp+moBYv7DT14+jtXQKOgGJpDqSqfS1RUKb/TBRXNDLGy02UScziWoAdE33zmf\n    UraVNwW8z1crxKA3yVw2Na++UqhGQlVLAbfXucqnJLVtNWKpkVQlezUgcfmFovsm\n    Iut+9MjI6/sZAqdXTLKuCKo0XjWzNKwnRecE0CYsCwzc80MvFYEiwQi1C0kwoouC\n    iOi8MKM/jDok+5/a3nQ7X+/ho5sbApNCJpfSXAK9YOJ3ju93+RjNuvORfp4/sW3W\n    OGXw6X30Ym7WS/7oYuwEILyqdyNOvKU7a+17d/W/YA60NOdA4iJI3aTfYFMD3l14\n    Da+D/wkTlEN3Ye7GN21A9AsZwWWiT9G5FOxWWVv7nTPG+Ix5ewehQWt/3DxhSizR\n    inMBizL5xpwx9LRWHnXX277lChYmPFAAMXINl1hnX6s0EY9pSDHN0IddibJkNKBD\n    m1CN37rqxoXQz4zoAyJGfQVkakqe16ayqI9yuQwO6AUkZcD5DYQdz9QYOTnYrQc6\n    6haC3D0Fmqg1s4v+6gpxZA/qTri0gVl/v/NN4Mk2/qWtK33imOedgD+5LXhZdBgJ\n    Mqn53AErG/AT622jvSb5UQKCAQEA/DTGLh0Ct97PCm+c+PxRFyieaHNJLWENKyxp\n    HoWGHfp2Bvt2Vphoi7GpRCM/yta4vCZgZmeWTQ0yBg6iPVPRA6Ho5hqh9OkUYVoh\n    prL3JsIU20jTutYjo2aefO4qXnJfkkXxNO2FElUHDTwtWdlGJQKvlUJwTv6xO19v\n    bQQkhZSpri6gIpi5Nkm2SGEtDofRJ+F6ThbQibEatL6DR00dh39MYQz+tZP5olzn\n    kX5bHEBWB7gy+YxTGF8FdlCSQTBBtNSKsAv3Cxj4qEHm+fu09vnH6fOZKenT2nXD\n    5QE/RpgQzLV1TumCjqLzqwp7bbzH+4mjsXpF3KHBZwnhMnDIRwKCAQEA63wYzjBy\n    no0GBBz0hOWrOwQ/AjUHfi47o3Xvl4RBjZclM171HKH7oMCnQvVKTNq8jvakCZjc\n    UI6i+H4R6aokiFS2xGbC2H3ZlSMFNwhb2xUs/C4Nr7JSOWZBtDy5QBspUsp26f7m\n    9VNVRzCmnxWV9be/1TxHDzDhslNlL5TMejbMorWnrtNG41KWwGtwvv2gApr3894j\n    eJNOh0WGfsMkXUM6+4v4WcCGrdV8Cr6Nvu96ZZe2PWu2dANtAfnxqogXXCoFE6r1\n    vie7hFSfJ2QR/vEbanED4pYGTtGYP1oseScx0u0hLhGLGccVBUNZlRbox4rIOELI\n    v9MLuiOL4YX7vQKCAQAGzMl3HtMe8AP3DRFXaT4qeK7ktA8KCS7YtibTatg14LXj\n    9E25gfx3n7+nlae3qVhrwkEhIbPcuflaTnSzYJonFet4oMkzGEGzakG0A+lEA0Ga\n    s/j5daKaWj71sVo1F7JZ+EbLnYfT+bTp93BllsUcZFkllhf/GUDgD++qKc1uSJbW\n    mm044ZNE0nH2u6ACX0kVYS/yAQ14WO0WaHiTqJGeQKFnkHkhni7B4O1hb923AkkP\n    hjjhn5Xx90Xnbb6zwUBURtLCcmAjzXWO29AFd3Lmoc9xEF9V0PckUb6JYyI4ngr9\n    6fqSuRsLC3u0ZeD0EX322zwtodVWYIodZBfNS1srAoIBAQCjTUPGeUKDQTjS0WGg\n    Z8T/AErRtQSlNFqXWMn2QPlUv2RE460HVi2xpOhZPtFvyqDIY7IOFbtzAfdya7rw\n    V9VN1bGJMdodV+jzy31qVJmerGit2SIUnYz30TnvS80L78oQZ+dfDi4MIuYYoFxs\n    JgQAipS1wz9kAXoCuGKLRJ0og6gVjfPjARE/w55XgiqFyEyWgfFBZOMkUsM6e7Rx\n    Y9Jr+puEpeRsGV9MXafPq6WQq3It0a/HmFLG0TlfDX3RzN6mQ12R7hTM8bDQa/6S\n    yorQSVPB1O3kzDVDo4X5KQd+XPfoVhmUYQYdsjmZlMMi6Og0uMFwgp/Epw6S3uO6\n    WbfhAoIBAQCOp4iIc87GyxWL8u6HrJaqmFlqkfou0hI+y9h6FfzsBYU6y3+gRYdF\n    wr2S9EUAb80kEQ1v0pt9417NOGc1pmYjKCZmDZ7qeGCGk2PR0U59+xJetXBWWhbq\n    5JxcwdRYoHyrmC/LINxzzqYOQbQevbW0zcEskeKfJsOtj9WJt6U9B1YZbE8pu2QV\n    xjvb+YekD2R+n/umV6eiaGfDau+EWudYVTqY0mR7y9hTiFR/KnqSsy2BUjljpacS\n    XBQO4ig7vY8+1+L3w2xpTN95/rXAvB4BbO/DLea9ArikePoSJ+bVTj0YwrKBghep\n    kOvbvVANrpsunlSAcpXm1qkV+G+xPnyJ\n    -----END PRIVATE KEY-----\ntype: kubernetes.io/tls\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/tls/test-ca.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBhzCCAS2gAwIBAgIUdsAtiX3gN0uk7ddxASWYE/tdv0wwCgYIKoZIzj0EAwIw\nGTEXMBUGA1UEAxMOZXhhbXBsZS5jb20gQ0EwHhcNMjAwNDE3MDgxODAwWhcNMjUw\nNDE2MDgxODAwWjAZMRcwFQYDVQQDEw5leGFtcGxlLmNvbSBDQTBZMBMGByqGSM49\nAgEGCCqGSM49AwEHA0IABK7h/5D8bV93MmEdhu02JsS6ugB8s6PzRl3PV4xs3Sbr\nRNkkM59+x3b0iWx/i76qPYpNLoiVUVXQmA9Y+4DbMxijUzBRMA4GA1UdDwEB/wQE\nAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQGyUiU1QEZiMAqjsnIYTwZ\n4yp5wzAPBgNVHREECDAGhwR/AAABMAoGCCqGSM49BAMCA0gAMEUCIQDzdtvKdE8O\n1+WRTZ9MuSiFYcrEz7Zne7VXouDEKqKEigIgM4WlbDeuNCKbqhqj+xZV0pa3rweb\nOD8EjjCMY69RMO0=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/tls/test-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIUT84jeO/ncOrqI+FY05Fzbg8Ed7MwDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTA4MDgxNDQyMzVaFw0yMjA4\nMDgxNDQyMzVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQDn/rPsZ74oypiwCzLlx57zplTiCi/WLSF+MmLGuTvM\nEQnV+OND2zFgvDIV/vFs3brkd6rLVI4NcdgSj4YKULCMwwOl45hQPdCTEPJvUhCm\nM+FuQ0czmEEJSjZtdLFz1B7QB/JemNnbfigxM9mlg58AlBhVJqn8q64wd/kC/W/K\nJTLJuBiVf12ZiPoPfO4WSxAqD3opZ8gdbmK0KYQAhKjEto6ZrYGisfwU1gt3l8M7\nsCJSpEkOkpuQgJ8D+xzJS36VXBJQMMP9nAPps+x/rGFplsPMsXEFFiwvR1+FJZwz\nlg2sJ91bLGZQ7vn74MfsGrxpiJwllRThJyT7C9V0sjb5trT2lEqZlP2dRSJYt7aJ\n1crEcdGSl6RIKgxSV6Hk8dh/ZaTjrTwaKxVkPo2IeEXy5xrR7DyonOQ6Yes0KOCm\nJB5yHkFlIVEnLm/HZXEtm3bPHsFgTZuInyBCOMXpUESuVZIw8YK+Vd6AExGPPwZ4\nn5I/sCDxWII9owIj3LeLzdUG6JoroahhGmo8rgpbJpPnS+VgryQ/raUQjqDzDCuE\n9vKXKBlSUqK6H9A+NMc0mme7M8/GX7T7ewFGUB/xsdrcO4yXjqHnAe0yLf8epDjC\nhh76bYqwwinVrmfcNcRxFVJZW2z0gGdgkOkOLaVVb9ggPV2SNAHbN4A+St/iRYR5\nawIDAQABo1MwUTAdBgNVHQ4EFgQUzMaCqVM30EZFfTeNUIJ5fNPAhaQwHwYDVR0j\nBBgwFoAUzMaCqVM30EZFfTeNUIJ5fNPAhaQwDwYDVR0TAQH/BAUwAwEB/zANBgkq\nhkiG9w0BAQsFAAOCAgEAVmk1rXtVkYR1Vs2Va/xrUaGXlFznhPU/Fft44kiEkkLp\nmLVelWyAqvXYioqssZwuZnTjGz0DQPqzJjqwuGy4CHwPLmhCtfHplrbWo8a0ivYC\ncL20KfZsG941siUh7LGBjTsq6mWBf2ytlFmg/fg93SgmqcEUAUcdps0JpZD8lgWB\nZMstfr6E3jaEus3OsvDD6hJNYZ5clJ5+ynLoWZ99A9JC0U46hmIZpRjbdSvasKpD\nXrXTdpzyL/Do3znXE/yfoHv4//Rj2CpPHJLYRCIzvuf1mo1fWd53FjHvrbUvaHFz\nCGuZROd4dC4Rx5nZw2ogIYvJ8m6HpIDkL3pBNSQJtIsvAYEQcotJoa5D/e9fu2Wr\n+og37oCY4OXzViEBQvyxKD4cajNco1fgGKEaFROADwr3JceGI7Anq5W+xdUvAGNM\nQuGeCueqNyrJ0CbQ1zEhwgpk/VYfB0u9m0bjMellRlKMdojby+FDCJtAJesx9no4\nSQXyx+aNHhj3qReysjGNwZvBk1IHL04HAT+ogNiYhTl1J/YON4MB5UN6Y2PxP6uG\nKvJGPigx4fAwfR/d78o5ngwoH9m+8FUg8+qllJ8XgIbl/VXKTk3G4ceOm4eBmrel\nDwWuBhELSjtXWPWhMlkiebgejDbAear53Lia2Cc43zx/KuhMHBTlKY/vY4F2YiI=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cmd/flux/testdata/create_secret/tls/test-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDn/rPsZ74oypiw\nCzLlx57zplTiCi/WLSF+MmLGuTvMEQnV+OND2zFgvDIV/vFs3brkd6rLVI4NcdgS\nj4YKULCMwwOl45hQPdCTEPJvUhCmM+FuQ0czmEEJSjZtdLFz1B7QB/JemNnbfigx\nM9mlg58AlBhVJqn8q64wd/kC/W/KJTLJuBiVf12ZiPoPfO4WSxAqD3opZ8gdbmK0\nKYQAhKjEto6ZrYGisfwU1gt3l8M7sCJSpEkOkpuQgJ8D+xzJS36VXBJQMMP9nAPp\ns+x/rGFplsPMsXEFFiwvR1+FJZwzlg2sJ91bLGZQ7vn74MfsGrxpiJwllRThJyT7\nC9V0sjb5trT2lEqZlP2dRSJYt7aJ1crEcdGSl6RIKgxSV6Hk8dh/ZaTjrTwaKxVk\nPo2IeEXy5xrR7DyonOQ6Yes0KOCmJB5yHkFlIVEnLm/HZXEtm3bPHsFgTZuInyBC\nOMXpUESuVZIw8YK+Vd6AExGPPwZ4n5I/sCDxWII9owIj3LeLzdUG6JoroahhGmo8\nrgpbJpPnS+VgryQ/raUQjqDzDCuE9vKXKBlSUqK6H9A+NMc0mme7M8/GX7T7ewFG\nUB/xsdrcO4yXjqHnAe0yLf8epDjChh76bYqwwinVrmfcNcRxFVJZW2z0gGdgkOkO\nLaVVb9ggPV2SNAHbN4A+St/iRYR5awIDAQABAoICAQCTxuixQ/wbW8IbEWcgeyHD\nLkaPndGO6jyVeF73GvL+MDRFuj558NvpNLfqzvTWVf9AnQGMd5Xs9oGegRHu7Csp\n3ucp+moBYv7DT14+jtXQKOgGJpDqSqfS1RUKb/TBRXNDLGy02UScziWoAdE33zmf\nUraVNwW8z1crxKA3yVw2Na++UqhGQlVLAbfXucqnJLVtNWKpkVQlezUgcfmFovsm\nIut+9MjI6/sZAqdXTLKuCKo0XjWzNKwnRecE0CYsCwzc80MvFYEiwQi1C0kwoouC\niOi8MKM/jDok+5/a3nQ7X+/ho5sbApNCJpfSXAK9YOJ3ju93+RjNuvORfp4/sW3W\nOGXw6X30Ym7WS/7oYuwEILyqdyNOvKU7a+17d/W/YA60NOdA4iJI3aTfYFMD3l14\nDa+D/wkTlEN3Ye7GN21A9AsZwWWiT9G5FOxWWVv7nTPG+Ix5ewehQWt/3DxhSizR\ninMBizL5xpwx9LRWHnXX277lChYmPFAAMXINl1hnX6s0EY9pSDHN0IddibJkNKBD\nm1CN37rqxoXQz4zoAyJGfQVkakqe16ayqI9yuQwO6AUkZcD5DYQdz9QYOTnYrQc6\n6haC3D0Fmqg1s4v+6gpxZA/qTri0gVl/v/NN4Mk2/qWtK33imOedgD+5LXhZdBgJ\nMqn53AErG/AT622jvSb5UQKCAQEA/DTGLh0Ct97PCm+c+PxRFyieaHNJLWENKyxp\nHoWGHfp2Bvt2Vphoi7GpRCM/yta4vCZgZmeWTQ0yBg6iPVPRA6Ho5hqh9OkUYVoh\nprL3JsIU20jTutYjo2aefO4qXnJfkkXxNO2FElUHDTwtWdlGJQKvlUJwTv6xO19v\nbQQkhZSpri6gIpi5Nkm2SGEtDofRJ+F6ThbQibEatL6DR00dh39MYQz+tZP5olzn\nkX5bHEBWB7gy+YxTGF8FdlCSQTBBtNSKsAv3Cxj4qEHm+fu09vnH6fOZKenT2nXD\n5QE/RpgQzLV1TumCjqLzqwp7bbzH+4mjsXpF3KHBZwnhMnDIRwKCAQEA63wYzjBy\nno0GBBz0hOWrOwQ/AjUHfi47o3Xvl4RBjZclM171HKH7oMCnQvVKTNq8jvakCZjc\nUI6i+H4R6aokiFS2xGbC2H3ZlSMFNwhb2xUs/C4Nr7JSOWZBtDy5QBspUsp26f7m\n9VNVRzCmnxWV9be/1TxHDzDhslNlL5TMejbMorWnrtNG41KWwGtwvv2gApr3894j\neJNOh0WGfsMkXUM6+4v4WcCGrdV8Cr6Nvu96ZZe2PWu2dANtAfnxqogXXCoFE6r1\nvie7hFSfJ2QR/vEbanED4pYGTtGYP1oseScx0u0hLhGLGccVBUNZlRbox4rIOELI\nv9MLuiOL4YX7vQKCAQAGzMl3HtMe8AP3DRFXaT4qeK7ktA8KCS7YtibTatg14LXj\n9E25gfx3n7+nlae3qVhrwkEhIbPcuflaTnSzYJonFet4oMkzGEGzakG0A+lEA0Ga\ns/j5daKaWj71sVo1F7JZ+EbLnYfT+bTp93BllsUcZFkllhf/GUDgD++qKc1uSJbW\nmm044ZNE0nH2u6ACX0kVYS/yAQ14WO0WaHiTqJGeQKFnkHkhni7B4O1hb923AkkP\nhjjhn5Xx90Xnbb6zwUBURtLCcmAjzXWO29AFd3Lmoc9xEF9V0PckUb6JYyI4ngr9\n6fqSuRsLC3u0ZeD0EX322zwtodVWYIodZBfNS1srAoIBAQCjTUPGeUKDQTjS0WGg\nZ8T/AErRtQSlNFqXWMn2QPlUv2RE460HVi2xpOhZPtFvyqDIY7IOFbtzAfdya7rw\nV9VN1bGJMdodV+jzy31qVJmerGit2SIUnYz30TnvS80L78oQZ+dfDi4MIuYYoFxs\nJgQAipS1wz9kAXoCuGKLRJ0og6gVjfPjARE/w55XgiqFyEyWgfFBZOMkUsM6e7Rx\nY9Jr+puEpeRsGV9MXafPq6WQq3It0a/HmFLG0TlfDX3RzN6mQ12R7hTM8bDQa/6S\nyorQSVPB1O3kzDVDo4X5KQd+XPfoVhmUYQYdsjmZlMMi6Og0uMFwgp/Epw6S3uO6\nWbfhAoIBAQCOp4iIc87GyxWL8u6HrJaqmFlqkfou0hI+y9h6FfzsBYU6y3+gRYdF\nwr2S9EUAb80kEQ1v0pt9417NOGc1pmYjKCZmDZ7qeGCGk2PR0U59+xJetXBWWhbq\n5JxcwdRYoHyrmC/LINxzzqYOQbQevbW0zcEskeKfJsOtj9WJt6U9B1YZbE8pu2QV\nxjvb+YekD2R+n/umV6eiaGfDau+EWudYVTqY0mR7y9hTiFR/KnqSsy2BUjljpacS\nXBQO4ig7vY8+1+L3w2xpTN95/rXAvB4BbO/DLea9ArikePoSJ+bVTj0YwrKBghep\nkOvbvVANrpsunlSAcpXm1qkV+G+xPnyJ\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_chart/basic.yaml",
    "content": "✚ generating HelmChart source\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmChart\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  chart: podinfo\n  interval: 0s\n  reconcileStrategy: ChartVersion\n  sourceRef:\n    kind: HelmRepository\n    name: podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_chart/setup-source.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmRepository\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  interval: 1m0s\n  provider: generic\n  type: oci\n  url: oci://ghcr.io/stefanprodan/charts\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_chart/verify_basic.yaml",
    "content": "✚ generating HelmChart source\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmChart\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  chart: podinfo\n  interval: 0s\n  reconcileStrategy: ChartVersion\n  sourceRef:\n    kind: HelmRepository\n    name: podinfo\n  verify:\n    provider: cosign\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_chart/verify_complete.yaml",
    "content": "✚ generating HelmChart source\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmChart\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  chart: podinfo\n  interval: 0s\n  reconcileStrategy: ChartVersion\n  sourceRef:\n    kind: HelmRepository\n    name: podinfo\n  verify:\n    matchOIDCIdentity:\n    - issuer: foo\n      subject: bar\n    provider: cosign\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_git/export.golden",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: default\nspec:\n  ignore: |-\n    .cosign\n    non-existent-dir/\n  interval: 1m0s\n  ref:\n    branch: master\n  sparseCheckout:\n  - .cosign\n  - non-existent-dir/\n  url: https://github.com/stefanprodan/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_git/source-git-branch-commit.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 1m0s\n  ref:\n    branch: main\n    commit: c88a2f41\n  url: https://github.com/stefanprodan/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_git/source-git-branch.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 1m0s\n  ref:\n    branch: test\n  url: https://github.com/stefanprodan/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_git/source-git-commit.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 1m0s\n  ref:\n    commit: c88a2f41\n  url: https://github.com/stefanprodan/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_git/source-git-provider-azure.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 1m0s\n  provider: azure\n  ref:\n    branch: test\n  url: https://dev.azure.com/foo/bar/_git/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_git/source-git-provider-generic.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 1m0s\n  provider: generic\n  ref:\n    branch: test\n  url: https://github.com/stefanprodan/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_git/source-git-provider-github.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 1m0s\n  provider: github\n  ref:\n    branch: test\n  secretRef:\n    name: appinfo\n  url: https://github.com/stefanprodan/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_git/source-git-refname.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 1m0s\n  ref:\n    name: refs/heads/main\n  url: https://github.com/stefanprodan/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_git/source-git-semver.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 1m0s\n  ref:\n    semver: v1.01\n  url: https://github.com/stefanprodan/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_git/source-git-tag.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 1m0s\n  ref:\n    tag: test\n  url: https://github.com/stefanprodan/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_git/success.golden",
    "content": "✚ generating GitRepository source\n► applying GitRepository source\n✔ GitRepository source created\n◎ waiting for GitRepository source reconciliation\n✔ GitRepository source reconciliation completed\n✔ fetched revision: v1\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_helm/https.golden",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmRepository\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m0s\n  url: https://stefanprodan.github.io/charts/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_helm/oci-with-secret.golden",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmRepository\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m0s\n  secretRef:\n    name: creds\n  type: oci\n  url: oci://ghcr.io/stefanprodan/charts/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_source_helm/oci.golden",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmRepository\nmetadata:\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m0s\n  type: oci\n  url: oci://ghcr.io/stefanprodan/charts/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/create_tenant/tenant-basic.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    toolkit.fluxcd.io/tenant: dev-team\n  name: apps\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    toolkit.fluxcd.io/tenant: dev-team\n  name: dev-team\n  namespace: apps\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    toolkit.fluxcd.io/tenant: dev-team\n  name: dev-team-reconciler\n  namespace: apps\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: User\n  name: gotk:apps:reconciler\n- kind: ServiceAccount\n  name: dev-team\n  namespace: apps\n"
  },
  {
    "path": "cmd/flux/testdata/create_tenant/tenant-with-cluster-role.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    toolkit.fluxcd.io/tenant: dev-team\n  name: apps\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    toolkit.fluxcd.io/tenant: dev-team\n  name: dev-team\n  namespace: apps\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    toolkit.fluxcd.io/tenant: dev-team\n  name: dev-team-reconciler\n  namespace: apps\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: custom-role\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: User\n  name: gotk:apps:reconciler\n- kind: ServiceAccount\n  name: dev-team\n  namespace: apps\n"
  },
  {
    "path": "cmd/flux/testdata/create_tenant/tenant-with-service-account.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    toolkit.fluxcd.io/tenant: dev-team\n  name: apps\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    toolkit.fluxcd.io/tenant: dev-team\n  name: flux-tenant\n  namespace: apps\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    toolkit.fluxcd.io/tenant: dev-team\n  name: dev-team-reconciler\n  namespace: apps\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: User\n  name: gotk:apps:reconciler\n- kind: ServiceAccount\n  name: flux-tenant\n  namespace: apps\n"
  },
  {
    "path": "cmd/flux/testdata/create_tenant/tenant-with-skip-namespace.yaml",
    "content": "---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    toolkit.fluxcd.io/tenant: dev-team\n  name: dev-team\n  namespace: apps\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    toolkit.fluxcd.io/tenant: dev-team\n  name: dev-team-reconciler\n  namespace: apps\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: User\n  name: gotk:apps:reconciler\n- kind: ServiceAccount\n  name: dev-team\n  namespace: apps\n"
  },
  {
    "path": "cmd/flux/testdata/debug_helmrelease/history-empty.golden.yaml",
    "content": "# History documentation: https://fluxcd.io/flux/components/helm/helmreleases/#history\n[]\n"
  },
  {
    "path": "cmd/flux/testdata/debug_helmrelease/history.golden.yaml",
    "content": "# History documentation: https://fluxcd.io/flux/components/helm/helmreleases/#history\n- appVersion: 6.0.0\n  chartName: podinfo\n  chartVersion: 6.0.0\n  configDigest: sha256:abc123\n  deleted: \"2024-01-01T10:00:00Z\"\n  digest: sha256:def456\n  firstDeployed: \"2024-01-01T09:00:00Z\"\n  lastDeployed: \"2024-01-01T10:00:00Z\"\n  name: test-with-history\n  namespace: {{ .fluxns }}\n  status: superseded\n  version: 1\n- appVersion: 6.1.0\n  chartName: podinfo\n  chartVersion: 6.1.0\n  configDigest: sha256:xyz789\n  deleted: null\n  digest: sha256:ghi012\n  firstDeployed: \"2024-01-01T11:00:00Z\"\n  lastDeployed: \"2024-01-01T11:00:00Z\"\n  name: test-with-history\n  namespace: {{ .fluxns }}\n  status: deployed\n  version: 2\n"
  },
  {
    "path": "cmd/flux/testdata/debug_helmrelease/objects.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: test-values-inline\n  namespace: {{ .fluxns }}\nspec:\n  chartRef:\n    kind: OCIRepository\n    name: podinfo\n  interval: 5m0s\n  values:\n    image:\n      repository: stefanprodan/podinfo\n      tag: 5.0.0\n---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: test-values-from\n  namespace: {{ .fluxns }}\nspec:\n  chartRef:\n    kind: OCIRepository\n    name: podinfo\n  interval: 5m0s\n  values:\n    image:\n      repository: stefanprodan/podinfo\n      tag: 5.0.0\n  valuesFrom:\n    - kind: ConfigMap\n      name: test\n    - kind: Secret\n      name: test\n      valuesKey: secrets.yaml\n    - kind: Secret\n      name: test\n      valuesKey: flatValue\n      targetPath: aFlatValue\n    - kind: ConfigMap\n      name: none\n      optional: true\n---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: test-with-history\n  namespace: {{ .fluxns }}\nspec:\n  chartRef:\n    kind: OCIRepository\n    name: podinfo\n  interval: 5m0s\n  values:\n    image:\n      repository: stefanprodan/podinfo\n      tag: 5.0.0\nstatus:\n  observedGeneration: 1\n  history:\n    - name: test-with-history\n      namespace: {{ .fluxns }}\n      version: 1\n      configDigest: sha256:abc123\n      chartName: podinfo\n      chartVersion: 6.0.0\n      appVersion: 6.0.0\n      deleted: \"2024-01-01T10:00:00Z\"\n      digest: sha256:def456\n      firstDeployed: \"2024-01-01T09:00:00Z\"\n      lastDeployed: \"2024-01-01T10:00:00Z\"\n      status: superseded\n    - name: test-with-history\n      namespace: {{ .fluxns }}\n      version: 2\n      configDigest: sha256:xyz789\n      chartName: podinfo\n      chartVersion: 6.1.0\n      appVersion: 6.1.0\n      digest: sha256:ghi012\n      firstDeployed: \"2024-01-01T11:00:00Z\"\n      lastDeployed: \"2024-01-01T11:00:00Z\"\n      status: deployed\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: test\n  namespace: {{ .fluxns }}\ndata:\n  values.yaml: |\n    cm: \"test\"\n    override: \"cm\"\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: test\n  namespace: {{ .fluxns }}\nstringData:\n  secrets.yaml: |\n    secret: \"test\"\n    override: \"secret\"\n  flatValue: some-flat-value\n"
  },
  {
    "path": "cmd/flux/testdata/debug_helmrelease/status.golden.yaml",
    "content": "# Status documentation: https://fluxcd.io/flux/components/helm/helmreleases/#helmrelease-status\nobservedGeneration: -1\n"
  },
  {
    "path": "cmd/flux/testdata/debug_helmrelease/values-from.golden.yaml",
    "content": "aFlatValue: some-flat-value\ncm: test\nimage:\n  repository: stefanprodan/podinfo\n  tag: 5.0.0\noverride: secret\nsecret: test\n"
  },
  {
    "path": "cmd/flux/testdata/debug_helmrelease/values-inline.golden.yaml",
    "content": "image:\n  repository: stefanprodan/podinfo\n  tag: 5.0.0\n"
  },
  {
    "path": "cmd/flux/testdata/debug_kustomization/history-empty.golden.yaml",
    "content": "# History documentation: https://fluxcd.io/flux/components/kustomize/kustomizations/#history\n[]\n"
  },
  {
    "path": "cmd/flux/testdata/debug_kustomization/history.golden.yaml",
    "content": "# History documentation: https://fluxcd.io/flux/components/kustomize/kustomizations/#history\n- digest: sha256:def456\n  firstReconciled: \"2024-01-01T09:00:00Z\"\n  lastReconciled: \"2024-01-01T10:00:00Z\"\n  lastReconciledDuration: 300ms\n  lastReconciledStatus: success\n  metadata:\n    originRevision: abc123\n  totalReconciliations: 1\n- digest: sha256:ghi012\n  firstReconciled: \"2024-02-01T09:00:00Z\"\n  lastReconciled: \"2024-02-01T10:00:00Z\"\n  lastReconciledDuration: 500ms\n  lastReconciledStatus: failure\n  metadata:\n    originRevision: xyz789\n  totalReconciliations: 10\n"
  },
  {
    "path": "cmd/flux/testdata/debug_kustomization/objects.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: test\n  namespace: {{ .fluxns }}\nspec:\n  sourceRef:\n    kind: GitRepository\n    name: test\n  interval: 1m\n  path: \"./\"\n  prune: true\n  postBuild:\n   substitute:\n     TEST_OVERRIDE: \"in-line\"\n     TEST_INLINE: \"in-line\"\n   substituteFrom:\n     - kind: ConfigMap\n       name: test\n     - kind: Secret\n       name: test\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: test-from\n  namespace: {{ .fluxns }}\nspec:\n  sourceRef:\n    kind: GitRepository\n    name: test\n  interval: 1m\n  path: \"./\"\n  prune: true\n  postBuild:\n    substituteFrom:\n      - kind: ConfigMap\n        name: test\n      - kind: Secret\n        name: test\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: test-with-history\n  namespace: {{ .fluxns }}\nspec:\n  sourceRef:\n    kind: GitRepository\n    name: test\n  interval: 1m\n  path: \"./\"\n  prune: true\n  postBuild:\n   substitute:\n     TEST_OVERRIDE: \"in-line\"\n     TEST_INLINE: \"in-line\"\n   substituteFrom:\n     - kind: ConfigMap\n       name: test\n     - kind: Secret\n       name: test\nstatus:\n  observedGeneration: 1\n  history:\n    - digest: sha256:def456\n      firstReconciled: \"2024-01-01T09:00:00Z\"\n      lastReconciled: \"2024-01-01T10:00:00Z\"\n      lastReconciledDuration: 300ms\n      lastReconciledStatus: success\n      metadata:\n        originRevision: abc123\n      totalReconciliations: 1\n    - digest: sha256:ghi012\n      firstReconciled: \"2024-02-01T09:00:00Z\"\n      lastReconciled: \"2024-02-01T10:00:00Z\"\n      lastReconciledDuration: 500ms\n      lastReconciledStatus: failure\n      metadata:\n        originRevision: xyz789\n      totalReconciliations: 10\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: test\n  namespace: {{ .fluxns }}\ndata:\n  TEST_OVERRIDE: \"cm\"\n  TEST_CM: \"cm\"\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: test\n  namespace: {{ .fluxns }}\nstringData:\n  TEST_OVERRIDE: \"secret\"\n  TEST_SECRET: \"secret\"\n"
  },
  {
    "path": "cmd/flux/testdata/debug_kustomization/status.golden.yaml",
    "content": "# Status documentation: https://fluxcd.io/flux/components/kustomize/kustomizations/#kustomization-status\nobservedGeneration: -1\n"
  },
  {
    "path": "cmd/flux/testdata/debug_kustomization/vars-from.golden.env",
    "content": "TEST_CM=cm\nTEST_OVERRIDE=secret\nTEST_SECRET=secret\n"
  },
  {
    "path": "cmd/flux/testdata/debug_kustomization/vars.golden.env",
    "content": "TEST_CM=cm\nTEST_INLINE=in-line\nTEST_OVERRIDE=in-line\nTEST_SECRET=secret\n"
  },
  {
    "path": "cmd/flux/testdata/diff-artifact/deployment-diff.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n      kustomize.toolkit.fluxcd.io/name: podinfo\n      kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo-diff\n  namespace: default\nspec:\n  minReadySeconds: 3\n  revisionHistoryLimit: 5\n  progressDeadlineSeconds: 60\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 0\n    type: RollingUpdate\n  selector:\n    matchLabels:\n      app: podinfo\n  template:\n    metadata:\n      annotations:\n        prometheus.io/scrape: \"true\"\n        prometheus.io/port: \"9797\"\n      labels:\n        app: podinfo\n    spec:\n      containers:\n      - name: podinfod\n        image: ghcr.io/stefanprodan/podinfo:6.0.10\n        imagePullPolicy: IfNotPresent\n        ports:\n        - name: http\n          containerPort: 9898\n          protocol: TCP\n        - name: http-metrics\n          containerPort: 9797\n          protocol: TCP\n        - name: grpc\n          containerPort: 9999\n          protocol: TCP\n        command:\n        - ./podinfo\n        - --port=9898\n        - --port-metrics=9797\n        - --grpc-port=9999\n        - --grpc-service-name=podinfo\n        - --level=info\n        - --random-delay=false\n        - --random-error=false\n        env:\n        - name: PODINFO_UI_COLOR\n          value: \"#34577c\"\n        livenessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/healthz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        readinessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/readyz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        resources:\n          limits:\n            cpu: 2000m\n            memory: 512Mi\n          requests:\n            cpu: 100m\n            memory: 64Mi\n"
  },
  {
    "path": "cmd/flux/testdata/diff-artifact/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n      kustomize.toolkit.fluxcd.io/name: podinfo\n      kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: default\nspec:\n  minReadySeconds: 3\n  revisionHistoryLimit: 5\n  progressDeadlineSeconds: 60\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 0\n    type: RollingUpdate\n  selector:\n    matchLabels:\n      app: podinfo\n  template:\n    metadata:\n      annotations:\n        prometheus.io/scrape: \"true\"\n        prometheus.io/port: \"9797\"\n      labels:\n        app: podinfo\n    spec:\n      containers:\n      - name: podinfod\n        image: ghcr.io/stefanprodan/podinfo:6.0.10\n        imagePullPolicy: IfNotPresent\n        ports:\n        - name: http\n          containerPort: 9898\n          protocol: TCP\n        - name: http-metrics\n          containerPort: 9797\n          protocol: TCP\n        - name: grpc\n          containerPort: 9999\n          protocol: TCP\n        command:\n        - ./podinfo\n        - --port=9898\n        - --port-metrics=9797\n        - --grpc-port=9999\n        - --grpc-service-name=podinfo\n        - --level=info\n        - --random-delay=false\n        - --random-error=false\n        env:\n        - name: PODINFO_UI_COLOR\n          value: \"#34577c\"\n        livenessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/healthz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        readinessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/readyz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        resources:\n          limits:\n            cpu: 2000m\n            memory: 512Mi\n          requests:\n            cpu: 100m\n            memory: 64Mi\n"
  },
  {
    "path": "cmd/flux/testdata/diff-artifact/success.golden",
    "content": "✔ no changes detected\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n      kustomize.toolkit.fluxcd.io/name: podinfo\n      kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: default\nspec:\n  minReadySeconds: 3\n  revisionHistoryLimit: 5\n  progressDeadlineSeconds: 60\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 0\n    type: RollingUpdate\n  selector:\n    matchLabels:\n      app: podinfo\n  template:\n    metadata:\n      annotations:\n        prometheus.io/scrape: \"true\"\n        prometheus.io/port: \"9797\"\n      labels:\n        app: podinfo\n    spec:\n      containers:\n      - name: podinfod\n        image: ghcr.io/stefanprodan/podinfo:6.0.10\n        imagePullPolicy: IfNotPresent\n        ports:\n        - name: http\n          containerPort: 9898\n          protocol: TCP\n        - name: http-metrics\n          containerPort: 9797\n          protocol: TCP\n        - name: grpc\n          containerPort: 9999\n          protocol: TCP\n        command:\n        - ./podinfo\n        - --port=9898\n        - --port-metrics=9797\n        - --grpc-port=9999\n        - --grpc-service-name=podinfo\n        - --level=info\n        - --random-delay=false\n        - --random-error=false\n        env:\n        - name: PODINFO_UI_COLOR\n          value: \"#34577c\"\n        livenessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/healthz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        readinessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/readyz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n        resources:\n          limits:\n            cpu: 2000m\n            memory: 512Mi\n          requests:\n            cpu: 100m\n            memory: 64Mi\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/diff-with-deployment.golden",
    "content": "► HorizontalPodAutoscaler/default/podinfo created\n► Service/default/podinfo created\n► Secret/default/docker-secret created\n► Secret/default/secret-basic-auth-stringdata created\n► Secret/default/podinfo-token-77t89m9b67 created\n► Secret/default/db-user-pass-bkbd782d2c created\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/diff-with-dockerconfigjson-sops-secret.golden",
    "content": "► Deployment/default/podinfo created\n► HorizontalPodAutoscaler/default/podinfo created\n► Service/default/podinfo created\n► Secret/default/secret-basic-auth-stringdata created\n► Secret/default/podinfo-token-77t89m9b67 created\n► Secret/default/db-user-pass-bkbd782d2c created\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/diff-with-drifted-key-sops-secret.golden",
    "content": "► Deployment/default/podinfo created\n► HorizontalPodAutoscaler/default/podinfo created\n► Service/default/podinfo created\n► Secret/default/docker-secret created\n► Secret/default/secret-basic-auth-stringdata created\n► Secret/default/podinfo-token-77t89m9b67 drifted\n\ndata\n- one map entry removed:   + one map entry added:\ndrift-key: \"*****\"           token: \"*****\"\n\n► Secret/default/db-user-pass-bkbd782d2c created\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/diff-with-drifted-secret.golden",
    "content": "► Deployment/default/podinfo created\n► HorizontalPodAutoscaler/default/podinfo created\n► Service/default/podinfo created\n► Secret/default/docker-secret created\n► Secret/default/secret-basic-auth-stringdata created\n► Secret/default/podinfo-token-77t89m9b67 created\n► Secret/default/db-user-pass-bkbd782d2c drifted\n\ndata.password\n± value change\n- *** (before)\n+ *** (after)\n\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/diff-with-drifted-service.golden",
    "content": "► Deployment/default/podinfo created\n► HorizontalPodAutoscaler/default/podinfo created\n► Service/default/podinfo drifted\n\nspec.ports.http.port\n± value change\n- 9899\n+ 9898\n\n► Secret/default/docker-secret created\n► Secret/default/secret-basic-auth-stringdata created\n► Secret/default/podinfo-token-77t89m9b67 created\n► Secret/default/db-user-pass-bkbd782d2c created\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/diff-with-drifted-stringdata-sops-secret.golden",
    "content": "► Deployment/default/podinfo created\n► HorizontalPodAutoscaler/default/podinfo created\n► Service/default/podinfo created\n► Secret/default/docker-secret created\n► Secret/default/secret-basic-auth-stringdata drifted\n\ndata\n- one map entry removed:   + one map entry added:\nusername1: \"*****\"           username: \"*****\"\n\n► Secret/default/podinfo-token-77t89m9b67 created\n► Secret/default/db-user-pass-bkbd782d2c created\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/diff-with-drifted-value-sops-secret.golden",
    "content": "► Deployment/default/podinfo created\n► HorizontalPodAutoscaler/default/podinfo created\n► Service/default/podinfo created\n► Secret/default/docker-secret created\n► Secret/default/secret-basic-auth-stringdata created\n► Secret/default/db-user-pass-bkbd782d2c created\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/diff-with-recursive.golden",
    "content": "📁 Kustomization/default/my-app changed\n► ConfigMap/default/my-app created\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/dockerconfigjson-sops-secret.yaml",
    "content": "apiVersion: v1\ndata:\n  .dockerconfigjson: eyJtYXNrIjoiKipTT1BTKioifQ==\nkind: Secret\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: docker-secret\n  namespace: default\ntype: kubernetes.io/dockerconfigjson\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/flux-kustomization-multiobj.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: podinfo\n\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: podinfo\nspec:\n  interval: 5m0s\n  path: ./kustomize\n  force: true\n  prune: true\n  sourceRef:\n    kind: GitRepository\n    name: podinfo\n  targetNamespace: default\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/key-sops-secret.yaml",
    "content": "apiVersion: v1\ndata:\n  drift-key: bXktc2VjcmV0LXRva2VuCg==\nkind: Secret\nmetadata:\n  labels:\n      kustomize.toolkit.fluxcd.io/name: podinfo\n      kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo-token-77t89m9b67\n  namespace: default\ntype: Opaque\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- ./deployment.yaml\n- ./hpa.yaml\n- ./service.yaml\nsecretGenerator:\n - literals:\n   - username=admin\n   - password=1f2d1e2e67df\n   name: secret-basic-auth\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/my-app.yaml",
    "content": "---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: my-app\n  namespace: default\nspec:\n  interval: 5m0s\n  path: ./my-app\n  force: true\n  prune: true\n  sourceRef:\n    kind: GitRepository\n    name: podinfo\n  targetNamespace: default\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/nothing-is-deployed.golden",
    "content": "► Deployment/default/podinfo created\n► HorizontalPodAutoscaler/default/podinfo created\n► Service/default/podinfo created\n► Secret/default/docker-secret created\n► Secret/default/secret-basic-auth-stringdata created\n► Secret/default/podinfo-token-77t89m9b67 created\n► Secret/default/db-user-pass-bkbd782d2c created\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/secret.yaml",
    "content": "apiVersion: v1\ndata:\n  password: cGFzc3dvcmQK\n  username: YWRtaW4=\nkind: Secret\nmetadata:\n  labels:\n      kustomize.toolkit.fluxcd.io/name: podinfo\n      kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: db-user-pass-bkbd782d2c\n  namespace: default\ntype: Opaque\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n      kustomize.toolkit.fluxcd.io/name: podinfo\n      kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: default\nspec:\n  type: ClusterIP\n  selector:\n    app: podinfo\n  ports:\n    - name: http\n      port: 9899\n      protocol: TCP\n      targetPort: http\n    - port: 9999\n      targetPort: grpc\n      protocol: TCP\n      name: grpc\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/stringdata-sops-secret.yaml",
    "content": "apiVersion: v1\nkind: Secret\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: podinfo\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: secret-basic-auth-stringdata\n  namespace: default\nstringData:\n  password: KipTT1BTKio=\n  username1: KipTT1BTKio=\ntype: kubernetes.io/basic-auth\n"
  },
  {
    "path": "cmd/flux/testdata/diff-kustomization/value-sops-secret.yaml",
    "content": "apiVersion: v1\ndata:\n  token: ZHJpZnQtdmFsdWUK\nkind: Secret\nmetadata:\n  labels:\n      kustomize.toolkit.fluxcd.io/name: podinfo\n      kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo-token-77t89m9b67\n  namespace: default\ntype: Opaque\n"
  },
  {
    "path": "cmd/flux/testdata/envsubst/file.gold",
    "content": "apiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: test\n  namespace: flux-system\nspec:\n  ref:\n    branch: main\n  interval: 5m\n  url: ssh://git@github.com/example/test\n"
  },
  {
    "path": "cmd/flux/testdata/envsubst/file.yaml",
    "content": "apiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: ${REPO_NAME}\n  namespace: ${REPO_NAMESPACE:=flux-system}\nspec:\n  ref:\n    branch: main\n  interval: 5m\n  url: ssh://git@github.com/example/${REPO_NAME}\n"
  },
  {
    "path": "cmd/flux/testdata/export/alert.yaml",
    "content": "---\napiVersion: notification.toolkit.fluxcd.io/v1beta3\nkind: Alert\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  eventSeverity: info\n  eventSources:\n  - kind: GitRepository\n    name: '*'\n  - kind: Kustomization\n    name: '*'\n  providerRef:\n    name: slack\n  summary: Slacktest Notification\n"
  },
  {
    "path": "cmd/flux/testdata/export/bucket.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: Bucket\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  bucketName: podinfo\n  endpoint: s3.amazonaws.com\n  interval: 5m0s\n  provider: aws\n  region: us-east-1\n  timeout: 30s\n"
  },
  {
    "path": "cmd/flux/testdata/export/external-artifact.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: ExternalArtifact\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  sourceRef:\n    apiVersion: source.example.com/v1alpha1\n    kind: GitHubRelease\n    name: flux-system\n    namespace: {{ .fluxns }}\n"
  },
  {
    "path": "cmd/flux/testdata/export/git-repo.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m0s\n  ref:\n    branch: main\n  secretRef:\n    name: flux-system\n  timeout: 1m0s\n  url: ssh://git@github.com/example/repo\n"
  },
  {
    "path": "cmd/flux/testdata/export/helm-chart.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmChart\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  chart: podinfo\n  interval: 1m0s\n  reconcileStrategy: ChartVersion\n  sourceRef:\n    kind: HelmRepository\n    name: podinfo\n  version: '*'\n"
  },
  {
    "path": "cmd/flux/testdata/export/helm-release.yaml",
    "content": "---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  chart:\n    spec:\n      chart: podinfo\n      reconcileStrategy: ChartVersion\n      sourceRef:\n        kind: HelmRepository\n        name: flux-systen\n        namespace: {{ .fluxns }}\n      version: '*'\n  interval: 5m0s\n"
  },
  {
    "path": "cmd/flux/testdata/export/helm-repo.yaml",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmRepository\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m0s\n  provider: generic\n  timeout: 1m0s\n  url: https://stefanprodan.github.io/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/export/image-policy.yaml",
    "content": "---\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImagePolicy\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  digestReflectionPolicy: Never\n  imageRepositoryRef:\n    name: flux-system\n  policy:\n    semver:\n      range: 5.0.x\n"
  },
  {
    "path": "cmd/flux/testdata/export/image-repo.yaml",
    "content": "---\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImageRepository\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  exclusionList:\n  - ^.*\\.sig$\n  image: ghcr.io/test/podinfo\n  interval: 1m0s\n  provider: generic\n"
  },
  {
    "path": "cmd/flux/testdata/export/image-update.yaml",
    "content": "---\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImageUpdateAutomation\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  git:\n    commit:\n      author:\n        email: fluxcdbot@users.noreply.github.com\n        name: fluxcdbot\n  interval: 1m0s\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  update:\n    path: ./clusters/my-cluster\n    strategy: Setters\n"
  },
  {
    "path": "cmd/flux/testdata/export/ks.yaml",
    "content": "---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m0s\n  path: ./infrastructure/\n  prune: true\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n"
  },
  {
    "path": "cmd/flux/testdata/export/objects.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: notification.toolkit.fluxcd.io/v1beta3\nkind: Provider\nmetadata:\n  name: slack\n  namespace: {{ .fluxns }}\nspec:\n  type: slack\n  channel: 'A channel with spacess'\n  address: https://hooks.slack.com/services/mock\n---\napiVersion: notification.toolkit.fluxcd.io/v1beta3\nkind: Alert\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  summary: \"Slacktest Notification\"\n  providerRef:\n    name: slack\n  eventSeverity: info\n  eventSources:\n    - kind: \"GitRepository\"\n      name: \"*\"\n    - kind: \"Kustomization\"\n      name: \"*\"\n---\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImageRepository\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  image: ghcr.io/test/podinfo\n  interval: 1m0s\n---\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImagePolicy\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  imageRepositoryRef:\n    name: flux-system\n  policy:\n    semver:\n      range: 5.0.x\n---\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImageUpdateAutomation\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  interval: 1m0s\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  git:\n    commit:\n      author:\n        email: fluxcdbot@users.noreply.github.com\n        name: fluxcdbot\n      messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}'\n  update:\n    path: ./clusters/my-cluster\n    strategy: Setters\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  ref:\n    branch: main\n  secretRef:\n    name: flux-system\n  interval: 5m\n  url: ssh://git@github.com/example/repo\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  path: ./infrastructure/\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  interval: 5m\n  prune: true\n---\napiVersion: notification.toolkit.fluxcd.io/v1\nkind: Receiver\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  type: github\n  events:\n    - \"ping\"\n    - \"push\"\n  secretRef:\n    name: webhook-token\n  resources:\n    - kind: GitRepository\n      name: flux-system\n      namespace: flux-system\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmRepository\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m\n  timeout: 1m0s\n  url: https://stefanprodan.github.io/podinfo\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmChart\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  chart: podinfo\n  interval: 1m0s\n  reconcileStrategy: ChartVersion\n  sourceRef:\n    kind: HelmRepository\n    name: podinfo\n  version: '*'\n---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m\n  chart:\n    spec:\n      chart: podinfo\n      sourceRef:\n        kind: HelmRepository\n        name: flux-systen\n        namespace: {{ .fluxns }}\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: Bucket\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m\n  provider: aws\n  bucketName: podinfo\n  endpoint: s3.amazonaws.com\n  region: us-east-1\n  timeout: 30s\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: ExternalArtifact\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  sourceRef:\n    apiVersion: source.example.com/v1alpha1\n    kind: GitHubRelease\n    name: flux-system\n    namespace: {{ .fluxns }}\n"
  },
  {
    "path": "cmd/flux/testdata/export/provider.yaml",
    "content": "---\napiVersion: notification.toolkit.fluxcd.io/v1beta3\nkind: Provider\nmetadata:\n  name: slack\n  namespace: {{ .fluxns }}\nspec:\n  address: https://hooks.slack.com/services/mock\n  channel: A channel with spacess\n  type: slack\n"
  },
  {
    "path": "cmd/flux/testdata/export/receiver.yaml",
    "content": "---\napiVersion: notification.toolkit.fluxcd.io/v1\nkind: Receiver\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  events:\n  - ping\n  - push\n  interval: 10m0s\n  resources:\n  - kind: GitRepository\n    name: flux-system\n    namespace: flux-system\n  secretRef:\n    name: webhook-token\n  type: github\n"
  },
  {
    "path": "cmd/flux/testdata/get/get.golden",
    "content": "NAME          \tREVISION          \tSUSPENDED\tREADY\tMESSAGE                              \npodinfo       \tmain@sha1:696f056d\tFalse    \tTrue \tFetched revision: main@sha1:696f056d\t\npodinfo-shard1\tmain@sha1:696f056d\tFalse    \tTrue \tFetched revision: main@sha1:696f056d\t\npodinfo-shard2\tmain@sha1:696f056d\tFalse    \tTrue \tFetched revision: main@sha1:696f056d\t\n"
  },
  {
    "path": "cmd/flux/testdata/get/get_label_one.golden",
    "content": "NAME          \tREVISION          \tSUSPENDED\tREADY\tMESSAGE                              \npodinfo-shard1\tmain@sha1:696f056d\tFalse    \tTrue \tFetched revision: main@sha1:696f056d\t\n"
  },
  {
    "path": "cmd/flux/testdata/get/get_label_two.golden",
    "content": "NAME   \tREVISION          \tSUSPENDED\tREADY\tMESSAGE                              \npodinfo\tmain@sha1:696f056d\tFalse    \tTrue \tFetched revision: main@sha1:696f056d\t\n"
  },
  {
    "path": "cmd/flux/testdata/get/objects.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: flux-system\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  ref:\n    branch: main\n  secretRef:\n    name: flux-system\n  url: ssh://git@github.com/example/repo\n  interval: 5m\nstatus:\n  artifact:\n    lastUpdateTime: \"2021-08-01T04:28:42Z\"\n    revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\n    path: \"example\"\n    url: \"example\"\n    digest: sha1:696f056df216eea4f9401adbee0ff744d4df390f\n  conditions:\n    - lastTransitionTime: \"2021-07-20T00:48:16Z\"\n      message: 'Fetched revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f'\n      reason: GitOperationSucceed\n      status: \"True\"\n      type: Ready\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: flux-system\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n    sharding.fluxcd.io/key: shard1\n  name: podinfo-shard1\n  namespace: {{ .fluxns }}\nspec:\n  ref:\n    branch: main\n  secretRef:\n    name: flux-system\n  url: ssh://git@github.com/example/repo\n  interval: 5m\nstatus:\n  artifact:\n    lastUpdateTime: \"2021-08-01T04:28:42Z\"\n    revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\n    path: \"example\"\n    url: \"example\"\n    digest: sha1:696f056df216eea4f9401adbee0ff744d4df390f\n  conditions:\n    - lastTransitionTime: \"2021-07-20T00:48:16Z\"\n      message: 'Fetched revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f'\n      reason: GitOperationSucceed\n      status: \"True\"\n      type: Ready\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: flux-system\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n    sharding.fluxcd.io/key: shard2\n  name: podinfo-shard2\n  namespace: {{ .fluxns }}\nspec:\n  ref:\n    branch: main\n  secretRef:\n    name: flux-system\n  url: ssh://git@github.com/example/repo\n  interval: 5m\nstatus:\n  artifact:\n    lastUpdateTime: \"2021-08-01T04:28:42Z\"\n    revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\n    path: \"example\"\n    url: \"example\"\n    digest: sha1:696f056df216eea4f9401adbee0ff744d4df390f\n  conditions:\n    - lastTransitionTime: \"2021-07-20T00:48:16Z\"\n      message: 'Fetched revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f'\n      reason: GitOperationSucceed\n      status: \"True\"\n      type: Ready\n"
  },
  {
    "path": "cmd/flux/testdata/helmrelease/create_helmrelease_from_git.golden",
    "content": "✚ generating HelmRelease\n► applying HelmRelease\n✔ HelmRelease created\n◎ waiting for HelmRelease reconciliation\n✔ HelmRelease thrfg is ready\n✔ applied revision 6.3.5\n"
  },
  {
    "path": "cmd/flux/testdata/helmrelease/create_source_git.golden",
    "content": "✚ generating GitRepository source\n► applying GitRepository source\n✔ GitRepository source created\n◎ waiting for GitRepository source reconciliation\n✔ GitRepository source reconciliation completed\n✔ fetched revision: 6.3.5@sha1:67e2c98a60dc92283531412a9e604dd4bae005a9\n"
  },
  {
    "path": "cmd/flux/testdata/helmrelease/delete_helmrelease_from_git.golden",
    "content": "► deleting helmrelease thrfg in {{ .ns }} namespace\n✔ helmrelease deleted\n"
  },
  {
    "path": "cmd/flux/testdata/helmrelease/get_helmrelease_from_git.golden",
    "content": "NAME \tREVISION\tSUSPENDED\tREADY\tMESSAGE                                                                      \nthrfg\t6.3.5   \tFalse    \tTrue \tHelm install succeeded for release thrfg-1/thrfg.v1 with chart podinfo@6.3.5\t\n"
  },
  {
    "path": "cmd/flux/testdata/helmrelease/reconcile_helmrelease_from_git.golden",
    "content": "► annotating GitRepository thrfg in {{ .ns }} namespace\n✔ GitRepository annotated\n◎ waiting for GitRepository reconciliation\n✔ fetched revision 6.3.5@sha1:67e2c98a60dc92283531412a9e604dd4bae005a9\n► annotating HelmChart {{ .ns }}-thrfg in {{ .ns }} namespace\n✔ HelmChart annotated\n◎ waiting for HelmChart reconciliation\n✔ fetched revision 6.3.5\n► annotating HelmRelease thrfg in {{ .ns }} namespace\n✔ HelmRelease annotated\n◎ waiting for HelmRelease reconciliation\n✔ applied revision 6.3.5\n"
  },
  {
    "path": "cmd/flux/testdata/helmrelease/resume_helmrelease_from_git.golden",
    "content": "► resuming helmrelease thrfg in {{ .ns }} namespace\n✔ helmrelease resumed\n◎ waiting for HelmRelease reconciliation\n✔ HelmRelease thrfg reconciliation completed\n✔ applied revision 6.3.5\n"
  },
  {
    "path": "cmd/flux/testdata/helmrelease/suspend_helmrelease_from_git.golden",
    "content": "► suspending helmrelease thrfg in {{ .ns }} namespace\n✔ helmrelease suspended\n"
  },
  {
    "path": "cmd/flux/testdata/image/create_image_policy.golden",
    "content": "✚ generating ImagePolicy\n► applying ImagePolicy\n✔ ImagePolicy created\n◎ waiting for ImagePolicy reconciliation\n✔ ImagePolicy reconciliation completed\n"
  },
  {
    "path": "cmd/flux/testdata/image/create_image_repository.golden",
    "content": "✚ generating ImageRepository\n► applying ImageRepository\n✔ ImageRepository created\n◎ waiting for ImageRepository reconciliation\n✔ ImageRepository reconciliation completed\n"
  },
  {
    "path": "cmd/flux/testdata/image/get_image_policy_regex.golden",
    "content": "NAME         \tIMAGE                       \tTAG  \tREADY\tMESSAGE                                                             \npodinfo-regex\tghcr.io/stefanprodan/podinfo\t5.0.0\tTrue \tLatest image tag for ghcr.io/stefanprodan/podinfo resolved to 5.0.0\t\n"
  },
  {
    "path": "cmd/flux/testdata/image/get_image_policy_semver.golden",
    "content": "NAME          \tIMAGE                       \tTAG  \tREADY\tMESSAGE                                                                                                                                                 \npodinfo-semver\tghcr.io/stefanprodan/podinfo\t5.0.3\tTrue \tLatest image tag for ghcr.io/stefanprodan/podinfo resolved to 5.0.3 with digest sha256:8704da90172710d422af855049175c1a8295731cbe2ad3b9a1c1074feecf8c10\t\n"
  },
  {
    "path": "cmd/flux/testdata/image/reconcile_image_policy.golden",
    "content": "► annotating ImagePolicy podinfo-semver in tis-2 namespace\n✔ ImagePolicy annotated\n◎ waiting for ImagePolicy reconciliation\n✔ selected ref ghcr.io/stefanprodan/podinfo:5.0.3@sha256:8704da90172710d422af855049175c1a8295731cbe2ad3b9a1c1074feecf8c10\n"
  },
  {
    "path": "cmd/flux/testdata/image/resume_image_policy.golden",
    "content": "► resuming image policy podinfo-semver in tis-2 namespace\n✔ image policy resumed\n◎ waiting for ImagePolicy reconciliation\n✔ ImagePolicy podinfo-semver reconciliation completed\n✔ selected ref ghcr.io/stefanprodan/podinfo:5.0.3@sha256:8704da90172710d422af855049175c1a8295731cbe2ad3b9a1c1074feecf8c10\n"
  },
  {
    "path": "cmd/flux/testdata/image/suspend_image_policy.golden",
    "content": "► suspending image policy podinfo-semver in tis-2 namespace\n✔ image policy suspended\n"
  },
  {
    "path": "cmd/flux/testdata/kustomization/create_kustomization_from_git.golden",
    "content": "✚ generating Kustomization\n► applying Kustomization\n✔ Kustomization created\n◎ waiting for Kustomization reconciliation\n✔ Kustomization tkfg is ready\n✔ applied revision 6.3.5@sha1:67e2c98a60dc92283531412a9e604dd4bae005a9\n"
  },
  {
    "path": "cmd/flux/testdata/kustomization/create_source_git.golden",
    "content": "✚ generating GitRepository source\n► applying GitRepository source\n✔ GitRepository source created\n◎ waiting for GitRepository source reconciliation\n✔ GitRepository source reconciliation completed\n✔ fetched revision: 6.3.5@sha1:67e2c98a60dc92283531412a9e604dd4bae005a9\n"
  },
  {
    "path": "cmd/flux/testdata/kustomization/delete_kustomization_from_git.golden",
    "content": "► deleting kustomization tkfg in {{ .ns }} namespace\n✔ kustomization deleted\n"
  },
  {
    "path": "cmd/flux/testdata/kustomization/get_kustomization_from_git.golden",
    "content": "NAME\tREVISION           \tSUSPENDED\tREADY\tMESSAGE                               \ntkfg\t6.3.5@sha1:67e2c98a\tFalse    \tTrue \tApplied revision: 6.3.5@sha1:67e2c98a\t\n"
  },
  {
    "path": "cmd/flux/testdata/kustomization/reconcile_kustomization_from_git.golden",
    "content": "► annotating GitRepository tkfg in {{ .ns }} namespace\n✔ GitRepository annotated\n◎ waiting for GitRepository reconciliation\n✔ fetched revision 6.3.5@sha1:67e2c98a60dc92283531412a9e604dd4bae005a9\n► annotating Kustomization tkfg in {{ .ns }} namespace\n✔ Kustomization annotated\n◎ waiting for Kustomization reconciliation\n✔ applied revision 6.3.5@sha1:67e2c98a60dc92283531412a9e604dd4bae005a9\n"
  },
  {
    "path": "cmd/flux/testdata/kustomization/resume_kustomization_from_git.golden",
    "content": "► resuming kustomization tkfg in {{ .ns }} namespace\n✔ kustomization resumed\n◎ waiting for Kustomization reconciliation\n✔ Kustomization tkfg reconciliation completed\n✔ applied revision 6.3.5@sha1:67e2c98a60dc92283531412a9e604dd4bae005a9\n"
  },
  {
    "path": "cmd/flux/testdata/kustomization/resume_kustomization_from_git_multiple_args.golden",
    "content": "► resuming kustomization tkfg in {{ .ns }} namespace\n✔ kustomization resumed\n"
  },
  {
    "path": "cmd/flux/testdata/kustomization/resume_kustomization_from_git_multiple_args_wait.golden",
    "content": "► resuming kustomization tkfg in {{ .ns }} namespace\n✔ kustomization resumed\n✗ Kustomization object 'tkfg' not found in {{ .ns }} namespace\n◎ waiting for Kustomization reconciliation\n✔ Kustomization tkfg reconciliation completed\n✔ applied revision 6.3.5@sha1:67e2c98a60dc92283531412a9e604dd4bae005a9\n"
  },
  {
    "path": "cmd/flux/testdata/kustomization/suspend_kustomization_from_git.golden",
    "content": "► suspending kustomization tkfg in {{ .ns }} namespace\n✔ kustomization suspended\n"
  },
  {
    "path": "cmd/flux/testdata/kustomization/suspend_kustomization_from_git_multiple_args.golden",
    "content": "► suspending kustomization tkfg in {{ .ns }} namespace\n✔ kustomization suspended\n✗ Kustomization foo not found in {{ .ns }} namespace\n✗ Kustomization bar not found in {{ .ns }} namespace\n"
  },
  {
    "path": "cmd/flux/testdata/logs/all-logs.txt",
    "content": "2022-08-02T12:55:34.419Z info GitRepository/podinfo.default - no changes since last reconcilation: observed revision \n2022-08-02T12:56:04.679Z error GitRepository/flux-system.flux-system - no changes since last reconcilation: observed revision \n2022-08-02T12:56:34.961Z error Kustomization/flux-system.flux-system - no changes since last reconcilation: observed revision \n2022-08-02T12:56:34.961Z info Kustomization/podinfo.default - no changes since last reconcilation: observed revision \n2022-08-02T12:56:34.961Z info GitRepository/podinfo.default - no changes since last reconcilation: observed revision \n2022-08-02T12:56:34.961Z error Kustomization/podinfo.flux-system - no changes since last reconcilation: observed revision \n"
  },
  {
    "path": "cmd/flux/testdata/logs/kind.txt",
    "content": "2022-08-02T12:56:34.961Z error Kustomization/flux-system.flux-system - no changes since last reconcilation: observed revision \n2022-08-02T12:56:34.961Z error Kustomization/podinfo.flux-system - no changes since last reconcilation: observed revision \n"
  },
  {
    "path": "cmd/flux/testdata/logs/log-level.txt",
    "content": "2022-08-02T12:56:04.679Z error GitRepository/flux-system.flux-system - no changes since last reconcilation: observed revision \n2022-08-02T12:56:34.961Z error Kustomization/flux-system.flux-system - no changes since last reconcilation: observed revision \n2022-08-02T12:56:34.961Z error Kustomization/podinfo.flux-system - no changes since last reconcilation: observed revision \n"
  },
  {
    "path": "cmd/flux/testdata/logs/multiple-filters.txt",
    "content": "2022-08-02T12:56:34.961Z error Kustomization/podinfo.flux-system - no changes since last reconcilation: observed revision \n"
  },
  {
    "path": "cmd/flux/testdata/logs/namespace.txt",
    "content": "2022-08-02T12:55:34.419Z info GitRepository/podinfo.default - no changes since last reconcilation: observed revision \n2022-08-02T12:56:34.961Z info Kustomization/podinfo.default - no changes since last reconcilation: observed revision \n2022-08-02T12:56:34.961Z info GitRepository/podinfo.default - no changes since last reconcilation: observed revision \n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir/some-dir/another-file",
    "content": ""
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir/some-dir/another-file.yaml",
    "content": "\napiVersion: image.toolkit.fluxcd.io/v1beta2\nkind: ImageUpdateAutomation\n---\n\n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir/some-dir/another-file.yml",
    "content": "# This file has Windows line endings.\r\n\r\napiVersion: image.toolkit.fluxcd.io/v1beta2\r\nkind: ImageUpdateAutomation\r\n---\r\n\r\n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir/some-file",
    "content": ""
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir/some-file.yaml",
    "content": "apiVersion: image.toolkit.fluxcd.io/v1beta1\nkind: ImageRepository\n---\n\n\n\napiVersion: image.toolkit.fluxcd.io/v1beta2\nkind: ImagePolicy\n\n---\nspec:\n  wait: true\n  dependsOn:\n    - apiVersion: image.toolkit.fluxcd.io/v1beta1 # update this from v1beta1\n      kind: ImageRepository # there can be comments here too\n\n\n---\n\napiVersion: image.toolkit.fluxcd.io/v1/v2\nkind: ImagePolicy\n\n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir/some-file.yml",
    "content": "# This file has Windows line endings.\r\n\r\napiVersion: image.toolkit.fluxcd.io/v1beta2\r\nkind: ImageRepository\r\n---\r\n\r\n\r\n\r\napiVersion: image.toolkit.fluxcd.io/v1beta1\r\nkind: ImagePolicy\r\n\r\n\r\n---\r\n\r\napiVersion: image.toolkit.fluxcd.io/v1/v2\r\nkind: ImagePolicy\r\n\r\n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir.golden/some-dir/another-file",
    "content": ""
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir.golden/some-dir/another-file.yaml",
    "content": "\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImageUpdateAutomation\n---\n\n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir.golden/some-dir/another-file.yml",
    "content": "# This file has Windows line endings.\r\n\r\napiVersion: image.toolkit.fluxcd.io/v1\r\nkind: ImageUpdateAutomation\r\n---\r\n\r\n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir.golden/some-file",
    "content": ""
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir.golden/some-file.yaml",
    "content": "apiVersion: image.toolkit.fluxcd.io/v1\nkind: ImageRepository\n---\n\n\n\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImagePolicy\n\n---\nspec:\n  wait: true\n  dependsOn:\n    - apiVersion: image.toolkit.fluxcd.io/v1 # update this from v1beta1\n      kind: ImageRepository # there can be comments here too\n\n\n---\n\napiVersion: image.toolkit.fluxcd.io/v1/v2\nkind: ImagePolicy\n\n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir.golden/some-file.yml",
    "content": "# This file has Windows line endings.\r\n\r\napiVersion: image.toolkit.fluxcd.io/v1\r\nkind: ImageRepository\r\n---\r\n\r\n\r\n\r\napiVersion: image.toolkit.fluxcd.io/v1\r\nkind: ImagePolicy\r\n\r\n\r\n---\r\n\r\napiVersion: image.toolkit.fluxcd.io/v1/v2\r\nkind: ImagePolicy\r\n\r\n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/dir.output.golden",
    "content": "► starting migration of custom resources\n⚠️ skipping irregular file testdata/migrate/file-system/dir/some-dir/another-file-link.yaml\n⚠️ skipping irregular file testdata/migrate/file-system/dir/some-file-link.yaml\n✚ testdata/migrate/file-system/dir/some-dir/another-file.yaml:2: ImageUpdateAutomation v1beta2 -> v1\n✚ testdata/migrate/file-system/dir/some-dir/another-file.yml:3: ImageUpdateAutomation v1beta2 -> v1\n⚠️ testdata/migrate/file-system/dir/some-file.yaml:20: unexpected GroupVersion string: image.toolkit.fluxcd.io/v1/v2\n✚ testdata/migrate/file-system/dir/some-file.yaml:1: ImageRepository v1beta1 -> v1\n✚ testdata/migrate/file-system/dir/some-file.yaml:7: ImagePolicy v1beta2 -> v1\n✚ testdata/migrate/file-system/dir/some-file.yaml:14: ImageRepository v1beta1 -> v1\n⚠️ testdata/migrate/file-system/dir/some-file.yml:15: unexpected GroupVersion string: image.toolkit.fluxcd.io/v1/v2\n✚ testdata/migrate/file-system/dir/some-file.yml:3: ImageRepository v1beta2 -> v1\n✚ testdata/migrate/file-system/dir/some-file.yml:9: ImagePolicy v1beta1 -> v1\n✔ file testdata/migrate/file-system/dir/some-dir/another-file.yaml migrated successfully\n✔ file testdata/migrate/file-system/dir/some-dir/another-file.yml migrated successfully\n✔ file testdata/migrate/file-system/dir/some-file.yaml migrated successfully\n✔ file testdata/migrate/file-system/dir/some-file.yml migrated successfully\n✔ custom resources migrated successfully\n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/single-file-wrong-ext.json",
    "content": ""
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/single-file.yaml",
    "content": "apiVersion: image.toolkit.fluxcd.io/v1beta1\nkind: ImageRepository\n---\n\n\n\napiVersion: image.toolkit.fluxcd.io/v1beta2\nkind: ImagePolicy\n\n\n---\n\napiVersion: image.toolkit.fluxcd.io/v1/v2\nkind: ImagePolicy\n\n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/single-file.yaml.golden",
    "content": "apiVersion: image.toolkit.fluxcd.io/v1\nkind: ImageRepository\n---\n\n\n\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImagePolicy\n\n\n---\n\napiVersion: image.toolkit.fluxcd.io/v1/v2\nkind: ImagePolicy\n\n"
  },
  {
    "path": "cmd/flux/testdata/migrate/file-system/single-file.yaml.output.golden",
    "content": "► starting migration of custom resources\n⚠️ testdata/migrate/file-system/single-file.yaml:13: unexpected GroupVersion string: image.toolkit.fluxcd.io/v1/v2\n✚ testdata/migrate/file-system/single-file.yaml:1: ImageRepository v1beta1 -> v1\n✚ testdata/migrate/file-system/single-file.yaml:7: ImagePolicy v1beta2 -> v1\n✔ file testdata/migrate/file-system/single-file.yaml migrated successfully\n✔ custom resources migrated successfully\n"
  },
  {
    "path": "cmd/flux/testdata/oci/create_source_oci.golden",
    "content": "► applying OCIRepository\n✔ OCIRepository created\n◎ waiting for OCIRepository reconciliation\n✔ OCIRepository reconciliation completed\n✔ fetched revision: 6.3.5@sha256:6c959c51ccbb952e5fe4737563338a0aaf975675dcf812912cf09e5463181871\n"
  },
  {
    "path": "cmd/flux/testdata/oci/delete_oci.golden",
    "content": "► deleting source oci thrfg in {{ .ns }} namespace\n✔ source oci deleted\n"
  },
  {
    "path": "cmd/flux/testdata/oci/export.golden",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: OCIRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 10m0s\n  ref:\n    tag: 6.3.5\n  url: oci://ghcr.io/stefanprodan/manifests/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/oci/export_with_complete_verification.golden",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: OCIRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 0s\n  ref:\n    tag: 6.3.5\n  url: oci://ghcr.io/stefanprodan/manifests/podinfo\n  verify:\n    matchOIDCIdentity:\n    - issuer: github\n      subject: stefanprodan\n    provider: cosign\n"
  },
  {
    "path": "cmd/flux/testdata/oci/export_with_issuer.golden",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: OCIRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 0s\n  ref:\n    tag: 6.3.5\n  url: oci://ghcr.io/stefanprodan/manifests/podinfo\n  verify:\n    matchOIDCIdentity:\n    - issuer: github\n      subject: \"\"\n    provider: cosign\n"
  },
  {
    "path": "cmd/flux/testdata/oci/export_with_secret.golden",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: OCIRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 10m0s\n  ref:\n    tag: 6.3.5\n  secretRef:\n    name: creds\n  url: oci://ghcr.io/stefanprodan/manifests/podinfo\n"
  },
  {
    "path": "cmd/flux/testdata/oci/export_with_subject.golden",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: OCIRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 0s\n  ref:\n    tag: 6.3.5\n  url: oci://ghcr.io/stefanprodan/manifests/podinfo\n  verify:\n    matchOIDCIdentity:\n    - issuer: \"\"\n      subject: stefanprodan\n    provider: cosign\n"
  },
  {
    "path": "cmd/flux/testdata/oci/export_with_verify_secret.golden",
    "content": "---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: OCIRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 10m0s\n  ref:\n    tag: 6.3.5\n  url: oci://ghcr.io/stefanprodan/manifests/podinfo\n  verify:\n    provider: cosign\n    secretRef:\n      name: cosign-pub\n"
  },
  {
    "path": "cmd/flux/testdata/oci/get_oci.golden",
    "content": "NAME \tREVISION             \tSUSPENDED\tREADY\tMESSAGE                                            \nthrfg\t6.3.5@sha256:6c959c51\tFalse    \tTrue \tstored artifact for digest '6.3.5@sha256:6c959c51'\t\n"
  },
  {
    "path": "cmd/flux/testdata/oci/reconcile_oci.golden",
    "content": "► annotating OCIRepository thrfg in {{ .ns }} namespace\n✔ OCIRepository annotated\n◎ waiting for OCIRepository reconciliation\n✔ fetched revision 6.3.5@sha256:6c959c51ccbb952e5fe4737563338a0aaf975675dcf812912cf09e5463181871\n"
  },
  {
    "path": "cmd/flux/testdata/oci/resume_oci.golden",
    "content": "► resuming source oci thrfg in {{ .ns }} namespace\n✔ source oci resumed\n◎ waiting for OCIRepository reconciliation\n✔ OCIRepository thrfg reconciliation completed\n✔ fetched revision 6.3.5@sha256:6c959c51ccbb952e5fe4737563338a0aaf975675dcf812912cf09e5463181871\n"
  },
  {
    "path": "cmd/flux/testdata/oci/suspend_oci.golden",
    "content": "► suspending source oci thrfg in {{ .ns }} namespace\n✔ source oci suspended\n"
  },
  {
    "path": "cmd/flux/testdata/trace/deployment-hr-ocirepo.golden",
    "content": "\nObject:         deployment/podinfo\nNamespace:      {{ .ns }}\nStatus:         Managed by Flux\n---\nHelmRelease:    podinfo\nNamespace:      {{ .ns }}\nRevision:       6.3.5\nStatus:         Last reconciled at {{ .helmReleaseLastReconcile }}\nMessage:        Release reconciliation succeeded\n---\nOCIRepository:   podinfo-charts\nNamespace:       {{ .fluxns }}\nURL:             oci://ghcr.io/stefanprodan/charts/podinfo\nTag:             6.8.0\nRevision:        sha256:dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3\nStatus:          Last reconciled at {{ .ociRepositoryLastReconcile }}\nMessage:         stored artifact for digest 'sha256:dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3'\n"
  },
  {
    "path": "cmd/flux/testdata/trace/deployment-hr-ocirepo.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .ns }}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: podinfo\n    app.kubernetes.io/managed-by: Helm\n    helm.toolkit.fluxcd.io/name: podinfo\n    helm.toolkit.fluxcd.io/namespace: {{ .ns }}\n  name: podinfo\n  namespace: {{ .ns }}\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: podinfo\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: podinfo\n    spec:\n      containers:\n      - name: hello\n        command: [ \"echo hello world\" ]\n        image: busybox\n---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: podinfo\n  namespace: {{ .ns }}\nspec:\n  chartRef:\n    kind: OCIRepository\n    name: podinfo-charts\n    namespace: {{ .fluxns }}\n  interval: 5m\nstatus:\n  conditions:\n  - lastTransitionTime: \"2021-07-16T15:42:20Z\"\n    message: Release reconciliation succeeded\n    reason: ReconciliationSucceeded\n    status: \"True\"\n    type: Ready\n  lastAttemptedRevision: 6.3.5\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: OCIRepository\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: flux-system\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo-charts\n  namespace: {{ .fluxns }}\nspec:\n  interval: 10m0s\n  provider: generic\n  ref:\n    tag: 6.8.0\n  timeout: 60s\n  url: oci://ghcr.io/stefanprodan/charts/podinfo\nstatus:\n  artifact:\n    lastUpdateTime: \"2022-08-10T10:07:59Z\"\n    metadata:\n      org.opencontainers.image.revision: 6.1.6@sha1:450796ddb2ab6724ee1cc32a4be56da032d1cca0\n      org.opencontainers.image.source: https://github.com/stefanprodan/podinfo.git\n    path: \"example\"\n    revision: sha256:dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3\n    url: \"example\"\n    digest: sha256:dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3\n  conditions:\n  - lastTransitionTime: \"2021-07-20T00:48:16Z\"\n    message: \"stored artifact for digest 'sha256:dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3'\"\n    reason: Succeed\n    status: \"True\"\n    type: Ready\n"
  },
  {
    "path": "cmd/flux/testdata/trace/deployment.golden",
    "content": "\nObject:         deployment/podinfo\nNamespace:      {{ .ns }}\nStatus:         Managed by Flux\n---\nHelmRelease:    podinfo\nNamespace:      {{ .ns }}\nRevision:       6.3.5\nStatus:         Last reconciled at {{ .helmReleaseLastReconcile }}\nMessage:        Release reconciliation succeeded\n---\nHelmChart:      podinfo-podinfo\nNamespace:      {{ .fluxns }}\nChart:          podinfo\nVersion:        6.3.5\nRevision:       6.3.5\nStatus:         Last reconciled at {{ .helmChartLastReconcile }}\nMessage:        Fetched revision: 6.3.5\n---\nHelmRepository: podinfo\nNamespace:      {{ .fluxns }}\nURL:            https://stefanprodan.github.io/podinfo\nRevision:       sha1:8411f23d07d3701f0e96e7d9e503b7936d7e1d56\nStatus:         Last reconciled at {{ .helmRepositoryLastReconcile }}\nMessage:        Fetched revision: main@sha1:8411f23d07d3701f0e96e7d9e503b7936d7e1d56\n"
  },
  {
    "path": "cmd/flux/testdata/trace/deployment.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .ns }}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: podinfo\n    app.kubernetes.io/managed-by: Helm\n    helm.toolkit.fluxcd.io/name: podinfo\n    helm.toolkit.fluxcd.io/namespace: {{ .ns }}\n  name: podinfo\n  namespace: {{ .ns }}\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: podinfo\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: podinfo\n    spec:\n      containers:\n      - name: hello\n        command: [ \"echo hello world\" ]\n        image: busybox\n---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: infrastructure\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: {{ .ns }}\nspec:\n  chart:\n    spec:\n      chart: podinfo\n      sourceRef:\n        kind: HelmRepository\n        name: podinfo\n        namespace: {{ .fluxns }}\n  interval: 5m\nstatus:\n  conditions:\n  - lastTransitionTime: \"2021-07-16T15:42:20Z\"\n    message: Release reconciliation succeeded\n    reason: ReconciliationSucceeded\n    status: \"True\"\n    type: Ready\n  helmChart: {{ .fluxns }}/podinfo-podinfo\n  lastAttemptedRevision: 6.3.5\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmChart\nmetadata:\n  name: podinfo-podinfo\n  namespace: {{ .fluxns }}\nspec:\n  chart: podinfo\n  sourceRef:\n    kind: HelmRepository\n    name: podinfo\n  version: 6.3.5\n  interval: 5m\nstatus:\n  artifact:\n    checksum: cf13ba96773d9a879cd052c86e73199b3f96c854\n    lastUpdateTime: \"2021-08-01T04:42:55Z\"\n    revision: 6.3.5\n    path: \"example\"\n    url: \"example\"\n    digest: sha1:cf13ba96773d9a879cd052c86e73199b3f96c854\n  conditions:\n  - lastTransitionTime: \"2021-07-16T15:32:09Z\"\n    message: 'Fetched revision: 6.3.5'\n    reason: ChartPullSucceeded\n    status: \"True\"\n    type: Ready\n\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmRepository\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: infrastructure\n    kustomize.toolkit.fluxcd.io/namespace: flux-system\n  name: podinfo\n  namespace: {{ .fluxns }}\nspec:\n  interval: 5m\n  timeout: 1m0s\n  url: https://stefanprodan.github.io/podinfo\nstatus:\n  artifact:\n    checksum: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56\n    lastUpdateTime: \"2021-07-11T00:25:46Z\"\n    revision: sha1:8411f23d07d3701f0e96e7d9e503b7936d7e1d56\n    path: \"example\"\n    url: \"example\"\n    digest: sha256:f105fc5f3b58605631dc25497773d0a392b807cb220f32635fada3ce0dd81ad6\n  conditions:\n  - lastTransitionTime: \"2021-07-11T00:25:46Z\"\n    message: 'Fetched revision: main@sha1:8411f23d07d3701f0e96e7d9e503b7936d7e1d56'\n    reason: IndexationSucceed\n    status: \"True\"\n    type: Ready\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: infrastructure\n  namespace: {{ .fluxns }}\nspec:\n  path: ./infrastructure/\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  interval: 5m\n  prune: true\nstatus:\n  conditions:\n  - lastTransitionTime: \"2021-08-01T04:52:56Z\"\n    message: 'Applied revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f'\n    reason: ReconciliationSucceeded\n    status: \"True\"\n    type: Ready\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: flux-system\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  ref:\n    branch: main\n  secretRef:\n    name: flux-system\n  interval: 5m\n  url: ssh://git@github.com/example/repo\n"
  },
  {
    "path": "cmd/flux/testdata/trace/helmrelease-oci.golden",
    "content": "\nObject:          HelmRelease/podinfo\nNamespace:       {{ .ns }}\nStatus:          Managed by Flux\n---\nKustomization:   infrastructure\nNamespace:       {{ .fluxns }}\nPath:            ./infrastructure\nRevision:        main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\nStatus:          Last reconciled at {{ .kustomizationLastReconcile }}\nMessage:         Applied revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\n---\nOCIRepository:   flux-system\nNamespace:       {{ .fluxns }}\nURL:             oci://ghcr.io/example/repo\nTag:             1.2.3\nRevision:        sha256:dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3\nOrigin Revision: 6.1.6@sha1:450796ddb2ab6724ee1cc32a4be56da032d1cca0\nOrigin Source:   https://github.com/stefanprodan/podinfo.git\nStatus:          Last reconciled at {{ .ociRepositoryLastReconcile }}\nMessage:         stored artifact for digest 'sha256:dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3'\n"
  },
  {
    "path": "cmd/flux/testdata/trace/helmrelease-oci.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .ns }}\n---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: infrastructure\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: {{ .ns }}\nspec:\n  chart:\n    spec:\n      chart: podinfo\n      sourceRef:\n        kind: HelmRepository\n        name: podinfo\n        namespace: {{ .fluxns }}\n  interval: 5m\nstatus:\n  conditions:\n  - lastTransitionTime: \"2021-07-16T15:42:20Z\"\n    message: Release reconciliation succeeded\n    reason: ReconciliationSucceeded\n    status: \"True\"\n    type: Ready\n  helmChart: {{ .fluxns }}/podinfo-podinfo\n  lastAppliedRevision: 6.3.5\n  lastAttemptedRevision: 6.3.5\n  lastAttemptedValuesChecksum: c31db75d05b7515eba2eef47bd71038c74b2e531\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: infrastructure\n  namespace: {{ .fluxns }}\nspec:\n  path: ./infrastructure\n  sourceRef:\n    kind: OCIRepository\n    name: flux-system\n  interval: 5m\n  prune: false\nstatus:\n  conditions:\n  - lastTransitionTime: \"2021-08-01T04:52:56Z\"\n    message: 'Applied revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f'\n    reason: ReconciliationSucceeded\n    status: \"True\"\n    type: Ready\n  lastAppliedRevision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: OCIRepository\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: flux-system\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  interval: 10m0s\n  provider: generic\n  ref:\n    tag: 1.2.3\n  timeout: 60s\n  url: oci://ghcr.io/example/repo\nstatus:\n  artifact:\n    lastUpdateTime: \"2022-08-10T10:07:59Z\"\n    metadata:\n      org.opencontainers.image.revision: 6.1.6@sha1:450796ddb2ab6724ee1cc32a4be56da032d1cca0\n      org.opencontainers.image.source: https://github.com/stefanprodan/podinfo.git\n    path: \"example\"\n    revision: sha256:dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3\n    url: \"example\"\n    digest: sha256:dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3\n  conditions:\n  - lastTransitionTime: \"2021-07-20T00:48:16Z\"\n    message: \"stored artifact for digest 'sha256:dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3'\"\n    reason: Succeed\n    status: \"True\"\n    type: Ready\n"
  },
  {
    "path": "cmd/flux/testdata/trace/helmrelease.golden",
    "content": "\nObject:          HelmRelease/podinfo\nNamespace:       {{ .ns }}\nStatus:          Managed by Flux\n---\nKustomization:   infrastructure\nNamespace:       {{ .fluxns }}\nPath:            ./infrastructure\nRevision:        main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\nStatus:          Last reconciled at {{ .kustomizationLastReconcile }}\nMessage:         Applied revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\n---\nGitRepository:   flux-system\nNamespace:       {{ .fluxns }}\nURL:             ssh://git@github.com/example/repo\nBranch:          main\nRevision:        main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\nStatus:          Last reconciled at {{ .gitRepositoryLastReconcile }}\nMessage:         Fetched revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\n"
  },
  {
    "path": "cmd/flux/testdata/trace/helmrelease.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .ns }}\n---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: infrastructure\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: podinfo\n  namespace: {{ .ns }}\nspec:\n  chart:\n    spec:\n      chart: podinfo\n      sourceRef:\n        kind: HelmRepository\n        name: podinfo\n        namespace: {{ .fluxns }}\n  interval: 5m\nstatus:\n  conditions:\n  - lastTransitionTime: \"2021-07-16T15:42:20Z\"\n    message: Release reconciliation succeeded\n    reason: ReconciliationSucceeded\n    status: \"True\"\n    type: Ready\n  helmChart: {{ .fluxns }}/podinfo-podinfo\n  lastAppliedRevision: 6.3.5\n  lastAttemptedRevision: 6.3.5\n  lastAttemptedValuesChecksum: c31db75d05b7515eba2eef47bd71038c74b2e531\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: infrastructure\n  namespace: {{ .fluxns }}\nspec:\n  path: ./infrastructure\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  interval: 5m\n  prune: false\nstatus:\n  conditions:\n  - lastTransitionTime: \"2021-08-01T04:52:56Z\"\n    message: 'Applied revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f'\n    reason: ReconciliationSucceeded\n    status: \"True\"\n    type: Ready\n  lastAppliedRevision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  labels:\n    kustomize.toolkit.fluxcd.io/name: flux-system\n    kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  ref:\n    branch: main\n  secretRef:\n    name: flux-system\n  url: ssh://git@github.com/example/repo\n  interval: 5m\nstatus:\n  artifact:\n    lastUpdateTime: \"2021-08-01T04:28:42Z\"\n    revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f\n    path: \"example\"\n    url: \"example\"\n    digest: sha1:696f056df216eea4f9401adbee0ff744d4df390f\n  conditions:\n  - lastTransitionTime: \"2021-07-20T00:48:16Z\"\n    message: 'Fetched revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f'\n    reason: GitOperationSucceed\n    status: \"True\"\n    type: Ready\n"
  },
  {
    "path": "cmd/flux/testdata/tree/kustomizations.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .fluxns }}\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: flux-system\n  namespace: {{ .fluxns }}\nspec:\n  path: ./clusters/production\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  interval: 5m\n  prune: true\nstatus:\n  conditions:\n  - lastTransitionTime: \"2021-08-01T04:52:56Z\"\n    message: 'Applied revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f'\n    reason: ReconciliationSucceeded\n    status: \"True\"\n    type: Ready\n  inventory:\n    entries:\n      - id: _{{ .fluxns }}__Namespace\n        v: v1\n      - id: {{ .fluxns }}_helm-controller_apps_Deployment\n        v: v1\n      - id: {{ .fluxns }}_kustomize-controller_apps_Deployment\n        v: v1\n      - id: {{ .fluxns }}_notification-controller_apps_Deployment\n        v: v1\n      - id: {{ .fluxns }}_source-controller_apps_Deployment\n        v: v1\n      - id: {{ .fluxns }}_infrastructure_kustomize.toolkit.fluxcd.io_Kustomization\n        v: v1beta2\n      - id: {{ .fluxns }}_flux-system_source.toolkit.fluxcd.io_GitRepository\n        v: v1beta1\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: infrastructure\n  namespace: {{ .fluxns }}\nspec:\n  path: ./infrastructure/production\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  interval: 5m\n  prune: true\nstatus:\n  conditions:\n    - lastTransitionTime: \"2021-08-01T04:52:56Z\"\n      message: 'Applied revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f'\n      reason: ReconciliationSucceeded\n      status: \"True\"\n      type: Ready\n  inventory:\n    entries:\n      - id: _cert-manager__Namespace\n        v: v1\n      - id: cert-manager_cert-manager_source.toolkit.fluxcd.io_HelmRepository\n        v: v1beta1\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: empty\n  namespace: {{ .fluxns }}\nspec:\n  path: ./apps/todo\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  interval: 5m\n  prune: true\nstatus:\n  conditions:\n    - lastTransitionTime: \"2021-08-01T04:52:56Z\"\n      message: 'Applied revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f'\n      reason: ReconciliationSucceeded\n      status: \"True\"\n      type: Ready\n---\n"
  },
  {
    "path": "cmd/flux/testdata/tree/tree-compact.golden",
    "content": "Kustomization/{{ .fluxns }}/flux-system\n├── Kustomization/{{ .fluxns }}/infrastructure\n│   └── HelmRepository/cert-manager/cert-manager\n└── GitRepository/{{ .fluxns }}/flux-system\n\n"
  },
  {
    "path": "cmd/flux/testdata/tree/tree-empty.golden",
    "content": "Kustomization/{{ .fluxns }}/empty\n\n"
  },
  {
    "path": "cmd/flux/testdata/tree/tree.golden",
    "content": "Kustomization/{{ .fluxns }}/flux-system\n├── Namespace/{{ .fluxns }}\n├── Deployment/{{ .fluxns }}/helm-controller\n├── Deployment/{{ .fluxns }}/kustomize-controller\n├── Deployment/{{ .fluxns }}/notification-controller\n├── Deployment/{{ .fluxns }}/source-controller\n├── Kustomization/{{ .fluxns }}/infrastructure\n│   ├── Namespace/cert-manager\n│   └── HelmRepository/cert-manager/cert-manager\n└── GitRepository/{{ .fluxns }}/flux-system\n\n"
  },
  {
    "path": "cmd/flux/trace.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"text/template\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/cli-runtime/pkg/resource\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tfluxmeta \"github.com/fluxcd/pkg/apis/meta\"\n\t\"github.com/fluxcd/pkg/oci\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar traceCmd = &cobra.Command{\n\tUse:   \"trace <resource> <name> [<name> ...]\",\n\tShort: \"Trace in-cluster objects throughout the GitOps delivery pipeline\",\n\tLong: withPreviewNote(`The trace command shows how one or more objects are managed by Flux,\nfrom which source and revision they come, and what the latest reconciliation status is.\n\nYou can also trace multiple objects with different resource kinds using <resource>/<name> multiple times.`),\n\tExample: `  # Trace a Kubernetes Deployment\n  flux trace -n apps deployment my-app\n\n  # Trace a Kubernetes Pod and a config map\n  flux trace -n redis pod/redis-master-0 cm/redis\n\n  # Trace a Kubernetes global object\n  flux trace namespace redis\n\n  # Trace a Kubernetes custom resource\n  flux trace -n redis helmrelease redis\n  \n  # API Version and Kind can also be specified explicitly\n  # Note that either both, kind and api-version, or neither have to be specified.\n  flux trace redis --kind=helmrelease --api-version=helm.toolkit.fluxcd.io/v2 -n redis`,\n\tRunE: traceCmdRun,\n}\n\ntype traceFlags struct {\n\tapiVersion string\n\tkind       string\n}\n\nvar traceArgs = traceFlags{}\n\nfunc init() {\n\ttraceCmd.Flags().StringVar(&traceArgs.kind, \"kind\", \"\",\n\t\t\"the Kubernetes object kind, e.g. Deployment'\")\n\ttraceCmd.Flags().StringVar(&traceArgs.apiVersion, \"api-version\", \"\",\n\t\t\"the Kubernetes object API version, e.g. 'apps/v1'\")\n\trootCmd.AddCommand(traceCmd)\n}\n\nfunc traceCmdRun(cmd *cobra.Command, args []string) error {\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar objects []*unstructured.Unstructured\n\tif traceArgs.kind != \"\" || traceArgs.apiVersion != \"\" {\n\t\tvar obj *unstructured.Unstructured\n\t\tobj, err = getObjectStatic(ctx, kubeClient, args)\n\t\tobjects = []*unstructured.Unstructured{obj}\n\t} else {\n\t\tobjects, err = getObjectDynamic(args)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn traceObjects(ctx, kubeClient, objects)\n}\n\nfunc traceObjects(ctx context.Context, kubeClient client.Client, objects []*unstructured.Unstructured) error {\n\tfor i, obj := range objects {\n\t\terr := traceObject(ctx, kubeClient, obj)\n\t\tif err != nil {\n\t\t\trootCmd.PrintErrf(\"failed to trace %v/%v in namespace %v: %v\", obj.GetKind(), obj.GetName(), obj.GetNamespace(), err)\n\t\t}\n\t\tif i < len(objects)-1 {\n\t\t\trootCmd.Println(\"---\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc traceObject(ctx context.Context, kubeClient client.Client, obj *unstructured.Unstructured) error {\n\tif ks, ok := isOwnerManagedByFlux(ctx, kubeClient, obj, kustomizev1.GroupVersion.Group); ok {\n\t\treport, err := traceKustomization(ctx, kubeClient, ks, obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trootCmd.Print(report)\n\t\treturn nil\n\t}\n\n\tif hr, ok := isOwnerManagedByFlux(ctx, kubeClient, obj, helmv2.GroupVersion.Group); ok {\n\t\treport, err := traceHelm(ctx, kubeClient, hr, obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trootCmd.Print(report)\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"object not managed by Flux\")\n}\n\nfunc getObjectStatic(ctx context.Context, kubeClient client.Client, args []string) (*unstructured.Unstructured, error) {\n\tif len(args) < 1 {\n\t\treturn nil, fmt.Errorf(\"object name is required\")\n\t}\n\n\tif traceArgs.kind == \"\" {\n\t\treturn nil, fmt.Errorf(\"object kind is required (--kind)\")\n\t}\n\n\tif traceArgs.apiVersion == \"\" {\n\t\treturn nil, fmt.Errorf(\"object apiVersion is required (--api-version)\")\n\t}\n\n\tgv, err := schema.ParseGroupVersion(traceArgs.apiVersion)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invaild apiVersion: %w\", err)\n\t}\n\n\tobj := &unstructured.Unstructured{}\n\tobj.SetGroupVersionKind(schema.GroupVersionKind{\n\t\tGroup:   gv.Group,\n\t\tVersion: gv.Version,\n\t\tKind:    traceArgs.kind,\n\t})\n\n\tobjName := types.NamespacedName{\n\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\tName:      args[0],\n\t}\n\n\tif err = kubeClient.Get(ctx, objName, obj); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to find object: %w\", err)\n\t}\n\treturn obj, nil\n}\n\nfunc getObjectDynamic(args []string) ([]*unstructured.Unstructured, error) {\n\tr := resource.NewBuilder(kubeconfigArgs).\n\t\tUnstructured().\n\t\tNamespaceParam(*kubeconfigArgs.Namespace).DefaultNamespace().\n\t\tResourceTypeOrNameArgs(false, args...).\n\t\tContinueOnError().\n\t\tLatest().\n\t\tDo()\n\n\tif err := r.Err(); err != nil {\n\t\tif resource.IsUsageError(err) {\n\t\t\treturn nil, fmt.Errorf(\"either `<resource>/<name>` or `<resource> <name>` is required as an argument\")\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tinfos, err := r.Infos()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"x: %v\", err)\n\t}\n\tif len(infos) == 0 {\n\t\treturn nil, fmt.Errorf(\"failed to find object: %w\", err)\n\t}\n\n\tobjects := []*unstructured.Unstructured{}\n\tfor _, info := range infos {\n\t\tobj := &unstructured.Unstructured{}\n\t\tobj.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(info.Object)\n\t\tif err != nil {\n\t\t\treturn objects, err\n\t\t}\n\t\tobjects = append(objects, obj)\n\t}\n\treturn objects, nil\n}\n\nfunc traceKustomization(ctx context.Context, kubeClient client.Client, ksName types.NamespacedName, obj *unstructured.Unstructured) (string, error) {\n\tks := &kustomizev1.Kustomization{}\n\terr := kubeClient.Get(ctx, ksName, ks)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to find kustomization: %w\", err)\n\t}\n\tksReady := meta.FindStatusCondition(ks.Status.Conditions, fluxmeta.ReadyCondition)\n\n\tvar gitRepository *sourcev1.GitRepository\n\tvar ociRepository *sourcev1.OCIRepository\n\tvar externalArtifact *sourcev1.ExternalArtifact\n\tvar ksRepositoryReady *metav1.Condition\n\tswitch ks.Spec.SourceRef.Kind {\n\tcase sourcev1.GitRepositoryKind:\n\t\tgitRepository = &sourcev1.GitRepository{}\n\t\tsourceNamespace := ks.Namespace\n\t\tif ks.Spec.SourceRef.Namespace != \"\" {\n\t\t\tsourceNamespace = ks.Spec.SourceRef.Namespace\n\t\t}\n\t\terr = kubeClient.Get(ctx, types.NamespacedName{\n\t\t\tNamespace: sourceNamespace,\n\t\t\tName:      ks.Spec.SourceRef.Name,\n\t\t}, gitRepository)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to find GitRepository: %w\", err)\n\t\t}\n\t\tksRepositoryReady = meta.FindStatusCondition(gitRepository.Status.Conditions, fluxmeta.ReadyCondition)\n\tcase sourcev1.OCIRepositoryKind:\n\t\tociRepository = &sourcev1.OCIRepository{}\n\t\tsourceNamespace := ks.Namespace\n\t\tif ks.Spec.SourceRef.Namespace != \"\" {\n\t\t\tsourceNamespace = ks.Spec.SourceRef.Namespace\n\t\t}\n\t\terr = kubeClient.Get(ctx, types.NamespacedName{\n\t\t\tNamespace: sourceNamespace,\n\t\t\tName:      ks.Spec.SourceRef.Name,\n\t\t}, ociRepository)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to find OCIRepository: %w\", err)\n\t\t}\n\t\tksRepositoryReady = meta.FindStatusCondition(ociRepository.Status.Conditions, fluxmeta.ReadyCondition)\n\tcase sourcev1.ExternalArtifactKind:\n\t\texternalArtifact = &sourcev1.ExternalArtifact{}\n\t\tsourceNamespace := ks.Namespace\n\t\tif ks.Spec.SourceRef.Namespace != \"\" {\n\t\t\tsourceNamespace = ks.Spec.SourceRef.Namespace\n\t\t}\n\t\terr = kubeClient.Get(ctx, types.NamespacedName{\n\t\t\tNamespace: sourceNamespace,\n\t\t\tName:      ks.Spec.SourceRef.Name,\n\t\t}, externalArtifact)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to find ExternalArtifact: %w\", err)\n\t\t}\n\t\tif externalArtifact.Spec.SourceRef == nil {\n\t\t\treturn \"\", fmt.Errorf(\"ExternalArtifact %s/%s is missing spec.sourceRef\", externalArtifact.Namespace, externalArtifact.Name)\n\t\t}\n\t\tksRepositoryReady = meta.FindStatusCondition(externalArtifact.Status.Conditions, fluxmeta.ReadyCondition)\n\t}\n\n\tvar traceTmpl = `\nObject:          {{.ObjectName}}\n{{- if .ObjectNamespace }}\nNamespace:       {{.ObjectNamespace}}\n{{- end }}\nStatus:          Managed by Flux\n{{- if .Kustomization }}\n---\nKustomization:   {{.Kustomization.Name}}\nNamespace:       {{.Kustomization.Namespace}}\n{{- if .Kustomization.Spec.TargetNamespace }}\nTarget:          {{.Kustomization.Spec.TargetNamespace}}\n{{- end }}\nPath:            {{.Kustomization.Spec.Path}}\nRevision:        {{.Kustomization.Status.LastAppliedRevision}}\n{{- if .KustomizationReady }}\nStatus:          Last reconciled at {{.KustomizationReady.LastTransitionTime}}\nMessage:         {{.KustomizationReady.Message}}\n{{- else }}\nStatus:          Unknown\n{{- end }}\n{{- end }}\n{{- if .GitRepository }}\n---\nGitRepository:   {{.GitRepository.Name}}\nNamespace:       {{.GitRepository.Namespace}}\nURL:             {{.GitRepository.Spec.URL}}\n{{- if .GitRepository.Spec.Reference }}\n{{- if .GitRepository.Spec.Reference.Tag }}\nTag:             {{.GitRepository.Spec.Reference.Tag}}\n{{- else if .GitRepository.Spec.Reference.SemVer }}\nTag:             {{.GitRepository.Spec.Reference.SemVer}}\n{{- else if .GitRepository.Spec.Reference.Branch }}\nBranch:          {{.GitRepository.Spec.Reference.Branch}}\n{{- end }}\n{{- end }}\n{{- if .GitRepository.Status.Artifact }}\nRevision:        {{.GitRepository.Status.Artifact.Revision}}\n{{- end }}\n{{- if .RepositoryReady }}\n{{- if eq .RepositoryReady.Status \"False\" }}\nStatus:          Last reconciliation failed at {{.RepositoryReady.LastTransitionTime}}\n{{- else }}\nStatus:          Last reconciled at {{.RepositoryReady.LastTransitionTime}}\n{{- end }}\nMessage:         {{.RepositoryReady.Message}}\n{{- else }}\nStatus:          Unknown\n{{- end }}\n{{- end }}\n{{- if .OCIRepository }}\n---\nOCIRepository:   {{.OCIRepository.Name}}\nNamespace:       {{.OCIRepository.Namespace}}\nURL:             {{.OCIRepository.Spec.URL}}\n{{- if .OCIRepository.Spec.Reference }}\n{{- if .OCIRepository.Spec.Reference.Tag }}\nTag:             {{.OCIRepository.Spec.Reference.Tag}}\n{{- else if .OCIRepository.Spec.Reference.SemVer }}\nTag:             {{.OCIRepository.Spec.Reference.SemVer}}\n{{- else if .OCIRepository.Spec.Reference.Digest }}\nDigest:          {{.OCIRepository.Spec.Reference.Digest}}\n{{- end }}\n{{- end }}\n{{- if .OCIRepository.Status.Artifact }}\nRevision:        {{.OCIRepository.Status.Artifact.Revision}}\n{{- if .OCIRepository.Status.Artifact.Metadata }}\n{{- $metadata := .OCIRepository.Status.Artifact.Metadata }}\n{{- range $k, $v := .Annotations }}\n{{ with (index $metadata $v) }}{{ $k }}{{ . }}{{ end }}\n{{- end }}\n{{- end }}\n{{- end }}\n{{- if .RepositoryReady }}\n{{- if eq .RepositoryReady.Status \"False\" }}\nStatus:          Last reconciliation failed at {{.RepositoryReady.LastTransitionTime}}\n{{- else }}\nStatus:          Last reconciled at {{.RepositoryReady.LastTransitionTime}}\n{{- end }}\nMessage:         {{.RepositoryReady.Message}}\n{{- else }}\nStatus:          Unknown\n{{- end }}\n{{- end }}\n{{- if .ExternalArtifact }}\n---\nExternalArtifact:{{.ExternalArtifact.Name}}\nNamespace:       {{.ExternalArtifact.Namespace}}\nSource:          {{.ExternalArtifact.Spec.SourceRef.Kind}}/{{.ExternalArtifact.Spec.SourceRef.Namespace}}/{{.ExternalArtifact.Spec.SourceRef.Name}}\n{{- if .ExternalArtifact.Status.Artifact }}\nRevision:        {{.ExternalArtifact.Status.Artifact.Revision}}\n{{- if .ExternalArtifact.Status.Artifact.Metadata }}\n{{- $metadata := .ExternalArtifact.Status.Artifact.Metadata }}\n{{- range $k, $v := .Annotations }}\n{{ with (index $metadata $v) }}{{ $k }}{{ . }}{{ end }}\n{{- end }}\n{{- end }}\n{{- end }}\n{{- if .RepositoryReady }}\n{{- if eq .RepositoryReady.Status \"False\" }}\nStatus:          Last reconciliation failed at {{.RepositoryReady.LastTransitionTime}}\n{{- else }}\nStatus:          Last reconciled at {{.RepositoryReady.LastTransitionTime}}\n{{- end }}\nMessage:         {{.RepositoryReady.Message}}\n{{- else }}\nStatus:          Unknown\n{{- end }}\n{{- end }}\n`\n\n\ttraceResult := struct {\n\t\tObjectName         string\n\t\tObjectNamespace    string\n\t\tKustomization      *kustomizev1.Kustomization\n\t\tKustomizationReady *metav1.Condition\n\t\tGitRepository      *sourcev1.GitRepository\n\t\tOCIRepository      *sourcev1.OCIRepository\n\t\tExternalArtifact   *sourcev1.ExternalArtifact\n\t\tRepositoryReady    *metav1.Condition\n\t\tAnnotations        map[string]string\n\t}{\n\t\tObjectName:         obj.GetKind() + \"/\" + obj.GetName(),\n\t\tObjectNamespace:    obj.GetNamespace(),\n\t\tKustomization:      ks,\n\t\tKustomizationReady: ksReady,\n\t\tGitRepository:      gitRepository,\n\t\tOCIRepository:      ociRepository,\n\t\tExternalArtifact:   externalArtifact,\n\t\tRepositoryReady:    ksRepositoryReady,\n\t\tAnnotations:        map[string]string{\"Origin Source:   \": oci.SourceAnnotation, \"Origin Revision: \": oci.RevisionAnnotation},\n\t}\n\n\tt, err := template.New(\"tmpl\").Parse(traceTmpl)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar data bytes.Buffer\n\twriter := bufio.NewWriter(&data)\n\tif err := t.Execute(writer, traceResult); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif err := writer.Flush(); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn data.String(), nil\n}\n\nfunc traceHelm(ctx context.Context, kubeClient client.Client, hrName types.NamespacedName, obj *unstructured.Unstructured) (string, error) {\n\thr := &helmv2.HelmRelease{}\n\terr := kubeClient.Get(ctx, hrName, hr)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to find HelmRelease: %w\", err)\n\t}\n\thrReady := meta.FindStatusCondition(hr.Status.Conditions, fluxmeta.ReadyCondition)\n\n\tvar hrChart *sourcev1.HelmChart\n\tvar hrChartReady *metav1.Condition\n\tif chart := hr.Status.HelmChart; chart != \"\" {\n\t\thrChart = &sourcev1.HelmChart{}\n\t\terr = kubeClient.Get(ctx, utils.ParseNamespacedName(chart), hrChart)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to find HelmChart: %w\", err)\n\t\t}\n\t\thrChartReady = meta.FindStatusCondition(hrChart.Status.Conditions, fluxmeta.ReadyCondition)\n\t}\n\n\tvar hrGitRepository *sourcev1.GitRepository\n\tvar hrGitRepositoryReady *metav1.Condition\n\tvar hrHelmRepository *sourcev1.HelmRepository\n\tvar hrHelmRepositoryReady *metav1.Condition\n\tvar hrOCIRepository *sourcev1.OCIRepository\n\tvar hrOCIRepositoryReady *metav1.Condition\n\tvar hrExternalArtifact *sourcev1.ExternalArtifact\n\tvar hrExternalArtifactReady *metav1.Condition\n\tif hr.Spec.Chart == nil {\n\t\tif hr.Spec.ChartRef != nil {\n\t\t\tswitch hr.Spec.ChartRef.Kind {\n\t\t\tcase sourcev1.OCIRepositoryKind:\n\t\t\t\thrOCIRepository = &sourcev1.OCIRepository{}\n\t\t\t\tsourceNamespace := hr.Namespace\n\t\t\t\tif hr.Spec.ChartRef.Namespace != \"\" {\n\t\t\t\t\tsourceNamespace = hr.Spec.ChartRef.Namespace\n\t\t\t\t}\n\t\t\t\terr = kubeClient.Get(ctx, types.NamespacedName{\n\t\t\t\t\tNamespace: sourceNamespace,\n\t\t\t\t\tName:      hr.Spec.ChartRef.Name,\n\t\t\t\t}, hrOCIRepository)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", fmt.Errorf(\"failed to find OCIRepository: %w\", err)\n\t\t\t\t}\n\t\t\t\thrOCIRepositoryReady = meta.FindStatusCondition(hrOCIRepository.Status.Conditions, fluxmeta.ReadyCondition)\n\t\t\tcase sourcev1.ExternalArtifactKind:\n\t\t\t\thrExternalArtifact = &sourcev1.ExternalArtifact{}\n\t\t\t\tsourceNamespace := hr.Namespace\n\t\t\t\tif hr.Spec.ChartRef.Namespace != \"\" {\n\t\t\t\t\tsourceNamespace = hr.Spec.ChartRef.Namespace\n\t\t\t\t}\n\t\t\t\terr = kubeClient.Get(ctx, types.NamespacedName{\n\t\t\t\t\tNamespace: sourceNamespace,\n\t\t\t\t\tName:      hr.Spec.ChartRef.Name,\n\t\t\t\t}, hrExternalArtifact)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", fmt.Errorf(\"failed to find ExternalArtifact: %w\", err)\n\t\t\t\t}\n\t\t\t\tif hrExternalArtifact.Spec.SourceRef == nil {\n\t\t\t\t\treturn \"\", fmt.Errorf(\"ExternalArtifact %s/%s is missing spec.sourceRef\", hrExternalArtifact.Namespace, hrExternalArtifact.Name)\n\t\t\t\t}\n\t\t\t\thrExternalArtifactReady = meta.FindStatusCondition(hrExternalArtifact.Status.Conditions, fluxmeta.ReadyCondition)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif hr.Spec.Chart.Spec.SourceRef.Kind == sourcev1.GitRepositoryKind {\n\t\t\thrGitRepository = &sourcev1.GitRepository{}\n\t\t\tsourceNamespace := hr.Namespace\n\t\t\tif hr.Spec.Chart.Spec.SourceRef.Namespace != \"\" {\n\t\t\t\tsourceNamespace = hr.Spec.Chart.Spec.SourceRef.Namespace\n\t\t\t}\n\t\t\terr = kubeClient.Get(ctx, types.NamespacedName{\n\t\t\t\tNamespace: sourceNamespace,\n\t\t\t\tName:      hr.Spec.Chart.Spec.SourceRef.Name,\n\t\t\t}, hrGitRepository)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"failed to find GitRepository: %w\", err)\n\t\t\t}\n\t\t\thrGitRepositoryReady = meta.FindStatusCondition(hrGitRepository.Status.Conditions, fluxmeta.ReadyCondition)\n\t\t}\n\n\t\tif hr.Spec.Chart.Spec.SourceRef.Kind == sourcev1.HelmRepositoryKind {\n\t\t\thrHelmRepository = &sourcev1.HelmRepository{}\n\t\t\tsourceNamespace := hr.Namespace\n\t\t\tif hr.Spec.Chart.Spec.SourceRef.Namespace != \"\" {\n\t\t\t\tsourceNamespace = hr.Spec.Chart.Spec.SourceRef.Namespace\n\t\t\t}\n\t\t\terr = kubeClient.Get(ctx, types.NamespacedName{\n\t\t\t\tNamespace: sourceNamespace,\n\t\t\t\tName:      hr.Spec.Chart.Spec.SourceRef.Name,\n\t\t\t}, hrHelmRepository)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"failed to find HelmRepository: %w\", err)\n\t\t\t}\n\t\t\thrHelmRepositoryReady = meta.FindStatusCondition(hrHelmRepository.Status.Conditions, fluxmeta.ReadyCondition)\n\t\t}\n\t}\n\n\tvar traceTmpl = `\nObject:         {{.ObjectName}}\n{{- if .ObjectNamespace }}\nNamespace:      {{.ObjectNamespace}}\n{{- end }}\nStatus:         Managed by Flux\n{{- if .HelmRelease }}\n---\nHelmRelease:    {{.HelmRelease.Name}}\nNamespace:      {{.HelmRelease.Namespace}}\n{{- if .HelmRelease.Spec.TargetNamespace }}\nTarget:         {{.HelmRelease.Spec.TargetNamespace}}\n{{- end }}\nRevision:       {{.HelmRelease.Status.LastAttemptedRevision}}\n{{- if .HelmReleaseReady }}\nStatus:         Last reconciled at {{.HelmReleaseReady.LastTransitionTime}}\nMessage:        {{.HelmReleaseReady.Message}}\n{{- else }}\nStatus:         Unknown\n{{- end }}\n{{- end }}\n{{- if .HelmChart }}\n---\nHelmChart:      {{.HelmChart.Name}}\nNamespace:      {{.HelmChart.Namespace}}\nChart:          {{.HelmChart.Spec.Chart}}\nVersion:        {{.HelmChart.Spec.Version}}\n{{- if .HelmChart.Status.Artifact }}\nRevision:       {{.HelmChart.Status.Artifact.Revision}}\n{{- end }}\n{{- if .HelmChartReady }}\nStatus:         Last reconciled at {{.HelmChartReady.LastTransitionTime}}\nMessage:        {{.HelmChartReady.Message}}\n{{- else }}\nStatus:         Unknown\n{{- end }}\n{{- end }}\n{{- if .HelmRepository }}\n---\nHelmRepository: {{.HelmRepository.Name}}\nNamespace:      {{.HelmRepository.Namespace}}\nURL:            {{.HelmRepository.Spec.URL}}\n{{- if .HelmRepository.Status.Artifact }}\nRevision:       {{.HelmRepository.Status.Artifact.Revision}}\n{{- end }}\n{{- if .HelmRepositoryReady }}\nStatus:         Last reconciled at {{.HelmRepositoryReady.LastTransitionTime}}\nMessage:        {{.HelmRepositoryReady.Message}}\n{{- else }}\nStatus:         Unknown\n{{- end }}\n{{- end }}\n{{- if .GitRepository }}\n---\nGitRepository: {{.GitRepository.Name}}\nNamespace:     {{.GitRepository.Namespace}}\nURL:           {{.GitRepository.Spec.URL}}\n{{- if .GitRepository.Spec.Reference }}\n{{- if .GitRepository.Spec.Reference.Tag }}\nTag:           {{.GitRepository.Spec.Reference.Tag}}\n{{- else if .GitRepository.Spec.Reference.SemVer }}\nTag:           {{.GitRepository.Spec.Reference.SemVer}}\n{{- else if .GitRepository.Spec.Reference.Branch }}\nBranch:        {{.GitRepository.Spec.Reference.Branch}}\n{{- end }}\n{{- end }}\n{{- if .GitRepository.Status.Artifact }}\nRevision:      {{.GitRepository.Status.Artifact.Revision}}\n{{- end }}\n{{- if .GitRepositoryReady }}\n{{- if eq .GitRepositoryReady.Status \"False\" }}\nStatus:        Last reconciliation failed at {{.GitRepositoryReady.LastTransitionTime}}\n{{- else }}\nStatus:        Last reconciled at {{.GitRepositoryReady.LastTransitionTime}}\n{{- end }}\nMessage:       {{.GitRepositoryReady.Message}}\n{{- else }}\nStatus:        Unknown\n{{- end }}\n{{- end }}\n{{- if .OCIRepository }}\n---\nOCIRepository:   {{.OCIRepository.Name}}\nNamespace:       {{.OCIRepository.Namespace}}\nURL:             {{.OCIRepository.Spec.URL}}\n{{- if .OCIRepository.Spec.Reference }}\n{{- if .OCIRepository.Spec.Reference.Tag }}\nTag:             {{.OCIRepository.Spec.Reference.Tag}}\n{{- else if .OCIRepository.Spec.Reference.SemVer }}\nTag:             {{.OCIRepository.Spec.Reference.SemVer}}\n{{- else if .OCIRepository.Spec.Reference.Digest }}\nDigest:          {{.OCIRepository.Spec.Reference.Digest}}\n{{- end }}\n{{- end }}\n{{- if .OCIRepository.Status.Artifact }}\nRevision:        {{.OCIRepository.Status.Artifact.Revision}}\n{{- end }}\n{{- if .OCIRepositoryReady }}\n{{- if eq .OCIRepositoryReady.Status \"False\" }}\nStatus:          Last reconciliation failed at {{.OCIRepositoryReady.LastTransitionTime}}\n{{- else }}\nStatus:          Last reconciled at {{.OCIRepositoryReady.LastTransitionTime}}\n{{- end }}\nMessage:         {{.OCIRepositoryReady.Message}}\n{{- else }}\nStatus:          Unknown\n{{- end }}\n{{- end }}\n{{- if .ExternalArtifact }}\n---\nExternalArtifact:{{.ExternalArtifact.Name}}\nNamespace:       {{.ExternalArtifact.Namespace}}\nSource:          {{.ExternalArtifact.Spec.SourceRef.Kind}}/{{.ExternalArtifact.Spec.SourceRef.Namespace}}/{{.ExternalArtifact.Spec.SourceRef.Name}}\n{{- if .ExternalArtifact.Status.Artifact }}\nRevision:        {{.ExternalArtifact.Status.Artifact.Revision}}\n{{- end }}\n{{- if .ExternalArtifactReady }}\n{{- if eq .ExternalArtifactReady.Status \"False\" }}\nStatus:          Last reconciliation failed at {{.ExternalArtifactReady.LastTransitionTime}}\n{{- else }}\nStatus:          Last reconciled at {{.ExternalArtifactReady.LastTransitionTime}}\n{{- end }}\nMessage:         {{.ExternalArtifactReady.Message}}\n{{- else }}\nStatus:          Unknown\n{{- end }}\n{{- end }}\n`\n\n\ttraceResult := struct {\n\t\tObjectName            string\n\t\tObjectNamespace       string\n\t\tHelmRelease           *helmv2.HelmRelease\n\t\tHelmReleaseReady      *metav1.Condition\n\t\tHelmChart             *sourcev1.HelmChart\n\t\tHelmChartReady        *metav1.Condition\n\t\tGitRepository         *sourcev1.GitRepository\n\t\tGitRepositoryReady    *metav1.Condition\n\t\tHelmRepository        *sourcev1.HelmRepository\n\t\tHelmRepositoryReady   *metav1.Condition\n\t\tOCIRepository         *sourcev1.OCIRepository\n\t\tOCIRepositoryReady    *metav1.Condition\n\t\tExternalArtifact      *sourcev1.ExternalArtifact\n\t\tExternalArtifactReady *metav1.Condition\n\t\tAnnotations           map[string]string\n\t}{\n\t\tObjectName:            obj.GetKind() + \"/\" + obj.GetName(),\n\t\tObjectNamespace:       obj.GetNamespace(),\n\t\tHelmRelease:           hr,\n\t\tHelmReleaseReady:      hrReady,\n\t\tHelmChart:             hrChart,\n\t\tHelmChartReady:        hrChartReady,\n\t\tGitRepository:         hrGitRepository,\n\t\tGitRepositoryReady:    hrGitRepositoryReady,\n\t\tHelmRepository:        hrHelmRepository,\n\t\tHelmRepositoryReady:   hrHelmRepositoryReady,\n\t\tOCIRepository:         hrOCIRepository,\n\t\tOCIRepositoryReady:    hrOCIRepositoryReady,\n\t\tExternalArtifact:      hrExternalArtifact,\n\t\tExternalArtifactReady: hrExternalArtifactReady,\n\t}\n\n\tt, err := template.New(\"tmpl\").Parse(traceTmpl)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar data bytes.Buffer\n\twriter := bufio.NewWriter(&data)\n\tif err := t.Execute(writer, traceResult); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif err := writer.Flush(); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn data.String(), nil\n}\n\nfunc isManagedByFlux(obj *unstructured.Unstructured, group string) (types.NamespacedName, bool) {\n\tnameKey := fmt.Sprintf(\"%s/name\", group)\n\tnamespaceKey := fmt.Sprintf(\"%s/namespace\", group)\n\tnamespacedName := types.NamespacedName{}\n\n\tfor k, v := range obj.GetLabels() {\n\t\tif k == nameKey {\n\t\t\tnamespacedName.Name = v\n\t\t}\n\t\tif k == namespaceKey {\n\t\t\tnamespacedName.Namespace = v\n\t\t}\n\t}\n\n\tif namespacedName.Name == \"\" {\n\t\treturn namespacedName, false\n\t}\n\treturn namespacedName, true\n}\n\nfunc isOwnerManagedByFlux(ctx context.Context, kubeClient client.Client, obj *unstructured.Unstructured, group string) (types.NamespacedName, bool) {\n\tif n, ok := isManagedByFlux(obj, group); ok {\n\t\treturn n, true\n\t}\n\n\tnamespacedName := types.NamespacedName{}\n\tfor _, reference := range obj.GetOwnerReferences() {\n\t\towner := &unstructured.Unstructured{}\n\t\tgv, err := schema.ParseGroupVersion(reference.APIVersion)\n\t\tif err != nil {\n\t\t\treturn namespacedName, false\n\t\t}\n\n\t\towner.SetGroupVersionKind(schema.GroupVersionKind{\n\t\t\tGroup:   gv.Group,\n\t\t\tVersion: gv.Version,\n\t\t\tKind:    reference.Kind,\n\t\t})\n\n\t\townerName := types.NamespacedName{\n\t\t\tNamespace: obj.GetNamespace(),\n\t\t\tName:      reference.Name,\n\t\t}\n\n\t\terr = kubeClient.Get(ctx, ownerName, owner)\n\t\tif err != nil {\n\t\t\treturn namespacedName, false\n\t\t}\n\n\t\tif n, ok := isManagedByFlux(owner, group); ok {\n\t\t\treturn n, true\n\t\t}\n\n\t\tif len(owner.GetOwnerReferences()) > 0 {\n\t\t\treturn isOwnerManagedByFlux(ctx, kubeClient, owner, group)\n\t\t}\n\t}\n\n\treturn namespacedName, false\n}\n"
  },
  {
    "path": "cmd/flux/trace_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestTraceNoArgs(t *testing.T) {\n\tcmd := cmdTestCase{\n\t\targs:   \"trace\",\n\t\tassert: assertError(\"either `<resource>/<name>` or `<resource> <name>` is required as an argument\"),\n\t}\n\tcmd.runTestCmd(t)\n}\n\nfunc toLocalTime(t *testing.T, in string) string {\n\tts, err := time.Parse(time.RFC3339, in)\n\tif err != nil {\n\t\tt.Fatalf(\"Error converting golden test time '%s': %v\", in, err)\n\t}\n\treturn ts.Local().String()\n}\n\nfunc TestTrace(t *testing.T) {\n\tcases := []struct {\n\t\tname       string\n\t\targs       string\n\t\tobjectFile string\n\t\tgoldenFile string\n\t\ttmpl       map[string]string\n\t}{\n\t\t{\n\t\t\t\"Deployment\",\n\t\t\t\"trace podinfo --kind deployment --api-version=apps/v1\",\n\t\t\t\"testdata/trace/deployment.yaml\",\n\t\t\t\"testdata/trace/deployment.golden\",\n\t\t\tmap[string]string{\n\t\t\t\t\"ns\":                          allocateNamespace(\"podinfo\"),\n\t\t\t\t\"fluxns\":                      allocateNamespace(\"flux-system\"),\n\t\t\t\t\"helmReleaseLastReconcile\":    toLocalTime(t, \"2021-07-16T15:42:20Z\"),\n\t\t\t\t\"helmChartLastReconcile\":      toLocalTime(t, \"2021-07-16T15:32:09Z\"),\n\t\t\t\t\"helmRepositoryLastReconcile\": toLocalTime(t, \"2021-07-11T00:25:46Z\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"HelmRelease\",\n\t\t\t\"trace podinfo --kind HelmRelease --api-version=helm.toolkit.fluxcd.io/v2\",\n\t\t\t\"testdata/trace/helmrelease.yaml\",\n\t\t\t\"testdata/trace/helmrelease.golden\",\n\t\t\tmap[string]string{\n\t\t\t\t\"ns\":                         allocateNamespace(\"podinfo\"),\n\t\t\t\t\"fluxns\":                     allocateNamespace(\"flux-system\"),\n\t\t\t\t\"kustomizationLastReconcile\": toLocalTime(t, \"2021-08-01T04:52:56Z\"),\n\t\t\t\t\"gitRepositoryLastReconcile\": toLocalTime(t, \"2021-07-20T00:48:16Z\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"HelmRelease from OCI registry\",\n\t\t\t\"trace podinfo --kind HelmRelease --api-version=helm.toolkit.fluxcd.io/v2\",\n\t\t\t\"testdata/trace/helmrelease-oci.yaml\",\n\t\t\t\"testdata/trace/helmrelease-oci.golden\",\n\t\t\tmap[string]string{\n\t\t\t\t\"ns\":                         allocateNamespace(\"podinfo\"),\n\t\t\t\t\"fluxns\":                     allocateNamespace(\"flux-system\"),\n\t\t\t\t\"kustomizationLastReconcile\": toLocalTime(t, \"2021-08-01T04:52:56Z\"),\n\t\t\t\t\"ociRepositoryLastReconcile\": toLocalTime(t, \"2021-07-20T00:48:16Z\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"Deployment from HelmRelease from OCI registry\",\n\t\t\t\"trace podinfo --kind deployment --api-version=apps/v1\",\n\t\t\t\"testdata/trace/deployment-hr-ocirepo.yaml\",\n\t\t\t\"testdata/trace/deployment-hr-ocirepo.golden\",\n\t\t\tmap[string]string{\n\t\t\t\t\"ns\":                         allocateNamespace(\"podinfo\"),\n\t\t\t\t\"fluxns\":                     allocateNamespace(\"flux-system\"),\n\t\t\t\t\"helmReleaseLastReconcile\":   toLocalTime(t, \"2021-07-16T15:42:20Z\"),\n\t\t\t\t\"ociRepositoryLastReconcile\": toLocalTime(t, \"2021-07-20T00:48:16Z\"),\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttestEnv.CreateObjectFile(tc.objectFile, tc.tmpl, t)\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tc.args + \" -n=\" + tc.tmpl[\"ns\"],\n\t\t\t\tassert: assertGoldenTemplateFile(tc.goldenFile, tc.tmpl),\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/tree.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar treeCmd = &cobra.Command{\n\tUse:   \"tree\",\n\tShort: \"Print the resources reconciled by Flux\",\n\tLong:  withPreviewNote(`The tree command shows the list of resources reconciled by a Flux object.`),\n}\n\nfunc init() {\n\trootCmd.AddCommand(treeCmd)\n}\n"
  },
  {
    "path": "cmd/flux/tree_artifact.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar treeArtifactCmd = &cobra.Command{\n\tUse:   \"artifact\",\n\tShort: \"Print artifact objects reconciled by Flux\",\n\tLong:  `The tree artifact sub-commands print a list of artifact objects.`,\n}\n\nfunc init() {\n\ttreeCmd.AddCommand(treeArtifactCmd)\n}\n"
  },
  {
    "path": "cmd/flux/tree_artifact_generator.go",
    "content": "/*\nCopyright 2025 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/cli-utils/pkg/object\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\tswapi \"github.com/fluxcd/source-watcher/api/v2/v1beta1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/tree\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar treeArtifactGeneratorCmd = &cobra.Command{\n\tUse:   \"generator [name]\",\n\tShort: \"Print the inventory of an ArtifactGenerator\",\n\tLong:  withPreviewNote(`The tree command prints the ExternalArtifact list managed by an ArtifactGenerator.'`),\n\tExample: `  # Print the ExternalArtifacts managed by an ArtifactGenerator\n  flux tree artifact generator my-generator`,\n\tRunE:              treeArtifactGeneratorCmdRun,\n\tArgs:              cobra.ExactArgs(1),\n\tValidArgsFunction: resourceNamesCompletionFunc(swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)),\n}\n\ntype TreeArtifactGeneratorFlags struct {\n\toutput string\n}\n\nvar treeArtifactGeneratorArgs TreeArtifactGeneratorFlags\n\nfunc init() {\n\ttreeArtifactGeneratorCmd.Flags().StringVarP(&treeArtifactGeneratorArgs.output, \"output\", \"o\", \"\",\n\t\t\"the format in which the tree should be printed. can be 'json' or 'yaml'\")\n\ttreeArtifactCmd.AddCommand(treeArtifactGeneratorCmd)\n}\n\nfunc treeArtifactGeneratorCmdRun(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"generator name is required\")\n\t}\n\tname := args[0]\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tag := &swapi.ArtifactGenerator{}\n\terr = kubeClient.Get(ctx, client.ObjectKey{\n\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\tName:      name,\n\t}, ag)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkTree := tree.New(object.ObjMetadata{\n\t\tNamespace: ag.Namespace,\n\t\tName:      ag.Name,\n\t\tGroupKind: schema.GroupKind{Group: swapi.GroupVersion.Group, Kind: swapi.ArtifactGeneratorKind},\n\t})\n\n\tfor _, ea := range ag.Status.Inventory {\n\t\tkTree.Add(object.ObjMetadata{\n\t\t\tNamespace: ea.Namespace,\n\t\t\tName:      ea.Name,\n\t\t\tGroupKind: schema.GroupKind{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.ExternalArtifactKind},\n\t\t})\n\t}\n\n\tswitch treeArtifactGeneratorArgs.output {\n\tcase \"json\":\n\t\tdata, err := json.MarshalIndent(kTree, \"\", \"  \")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trootCmd.Println(string(data))\n\tcase \"yaml\":\n\t\tdata, err := yaml.Marshal(kTree)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trootCmd.Println(string(data))\n\tdefault:\n\t\trootCmd.Println(kTree.Print())\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/tree_kustomization.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client/apiutil\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/cli-utils/pkg/object\"\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tssautil \"github.com/fluxcd/pkg/ssa/utils\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/tree\"\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar treeKsCmd = &cobra.Command{\n\tUse:     \"kustomization [name]\",\n\tAliases: []string{\"ks\", \"kustomization\"},\n\tShort:   \"Print the resource inventory of a Kustomization\",\n\tLong:    withPreviewNote(`The tree command prints the resource list reconciled by a Kustomization.'`),\n\tExample: `  # Print the resources managed by the root Kustomization\n  flux tree kustomization flux-system\n\n  # Print the Flux resources managed by the root Kustomization\n  flux tree kustomization flux-system --compact`,\n\tRunE:              treeKsCmdRun,\n\tValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),\n}\n\ntype TreeKsFlags struct {\n\tcompact bool\n\toutput  string\n}\n\nvar treeKsArgs TreeKsFlags\n\nfunc init() {\n\ttreeKsCmd.Flags().BoolVar(&treeKsArgs.compact, \"compact\", false, \"list Flux resources only.\")\n\ttreeKsCmd.Flags().StringVarP(&treeKsArgs.output, \"output\", \"o\", \"\",\n\t\t\"the format in which the tree should be printed. can be 'json' or 'yaml'\")\n\ttreeCmd.AddCommand(treeKsCmd)\n}\n\nfunc treeKsCmdRun(cmd *cobra.Command, args []string) error {\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"kustomization name is required\")\n\t}\n\tname := args[0]\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tk := &kustomizev1.Kustomization{}\n\terr = kubeClient.Get(ctx, client.ObjectKey{\n\t\tNamespace: *kubeconfigArgs.Namespace,\n\t\tName:      name,\n\t}, k)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkTree := tree.New(object.ObjMetadata{\n\t\tNamespace: k.Namespace,\n\t\tName:      k.Name,\n\t\tGroupKind: schema.GroupKind{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind},\n\t})\n\n\terr = treeKustomization(ctx, kTree, k, kubeClient, treeKsArgs.compact)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch treeKsArgs.output {\n\tcase \"json\":\n\t\tdata, err := json.MarshalIndent(kTree, \"\", \"  \")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trootCmd.Println(string(data))\n\tcase \"yaml\":\n\t\tdata, err := yaml.Marshal(kTree)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trootCmd.Println(string(data))\n\tdefault:\n\t\trootCmd.Println(kTree.Print())\n\t}\n\n\treturn nil\n}\n\nfunc treeKustomization(ctx context.Context, tree tree.ObjMetadataTree, item *kustomizev1.Kustomization, kubeClient client.Client, compact bool) error {\n\tif item.Status.Inventory == nil || len(item.Status.Inventory.Entries) == 0 {\n\t\treturn nil\n\t}\n\n\tcompactGroup := \"toolkit.fluxcd.io\"\n\n\tfor _, entry := range item.Status.Inventory.Entries {\n\t\tobjMetadata, err := object.ParseObjMetadata(entry.ID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif compact && !strings.Contains(objMetadata.GroupKind.Group, compactGroup) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif objMetadata.GroupKind.Group == kustomizev1.GroupVersion.Group &&\n\t\t\tobjMetadata.GroupKind.Kind == kustomizev1.KustomizationKind &&\n\t\t\tobjMetadata.Namespace == item.Namespace &&\n\t\t\tobjMetadata.Name == item.Name {\n\t\t\tcontinue\n\t\t}\n\n\t\tks := tree.Add(objMetadata)\n\n\t\tif objMetadata.GroupKind.Group == helmv2.GroupVersion.Group &&\n\t\t\tobjMetadata.GroupKind.Kind == helmv2.HelmReleaseKind {\n\t\t\tobjects, err := getHelmReleaseInventory(\n\t\t\t\tctx, client.ObjectKey{\n\t\t\t\t\tNamespace: objMetadata.Namespace,\n\t\t\t\t\tName:      objMetadata.Name,\n\t\t\t\t}, kubeClient)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfor _, obj := range objects {\n\t\t\t\tif compact && !strings.Contains(obj.GroupKind.Group, compactGroup) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tks.Add(obj)\n\t\t\t}\n\t\t}\n\n\t\tif objMetadata.GroupKind.Group == kustomizev1.GroupVersion.Group &&\n\t\t\tobjMetadata.GroupKind.Kind == kustomizev1.KustomizationKind &&\n\t\t\t// skip kustomization if it targets a remote clusters\n\t\t\titem.Spec.KubeConfig == nil {\n\t\t\tk := &kustomizev1.Kustomization{}\n\t\t\terr = kubeClient.Get(ctx, client.ObjectKey{\n\t\t\t\tNamespace: objMetadata.Namespace,\n\t\t\t\tName:      objMetadata.Name,\n\t\t\t}, k)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to find object: %w\", err)\n\t\t\t}\n\t\t\terr := treeKustomization(ctx, ks, k, kubeClient, compact)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype hrStorage struct {\n\tName     string `json:\"name,omitempty\"`\n\tManifest string `json:\"manifest,omitempty\"`\n}\n\nfunc getHelmReleaseInventory(ctx context.Context, objectKey client.ObjectKey, kubeClient client.Client) ([]object.ObjMetadata, error) {\n\thr := &helmv2.HelmRelease{}\n\tif err := kubeClient.Get(ctx, objectKey, hr); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// skip release if it targets a remote clusters\n\tif hr.Spec.KubeConfig != nil {\n\t\treturn nil, nil\n\t}\n\n\tstorageNamespace := hr.Status.StorageNamespace\n\tlatest := hr.Status.History.Latest()\n\tif len(storageNamespace) == 0 || latest == nil {\n\t\t// Skip release if it has no current\n\t\treturn nil, nil\n\t}\n\n\tstorageKey := client.ObjectKey{\n\t\tNamespace: storageNamespace,\n\t\tName:      fmt.Sprintf(\"sh.helm.release.v1.%s.v%v\", latest.Name, latest.Version),\n\t}\n\n\tstorageSecret := &corev1.Secret{}\n\tif err := kubeClient.Get(ctx, storageKey, storageSecret); err != nil {\n\t\t// skip release if it has no storage\n\t\tif apierrors.IsNotFound(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to find the Helm storage object for HelmRelease '%s': %w\", objectKey.String(), err)\n\t}\n\n\treleaseData, releaseFound := storageSecret.Data[\"release\"]\n\tif !releaseFound {\n\t\treturn nil, fmt.Errorf(\"failed to decode the Helm storage object for HelmRelease '%s'\", objectKey.String())\n\t}\n\n\t// adapted from https://github.com/helm/helm/blob/02685e94bd3862afcb44f6cd7716dbeb69743567/pkg/storage/driver/util.go\n\tvar b64 = base64.StdEncoding\n\tb, err := b64.DecodeString(string(releaseData))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar magicGzip = []byte{0x1f, 0x8b, 0x08}\n\tif bytes.Equal(b[0:3], magicGzip) {\n\t\tr, err := gzip.NewReader(bytes.NewReader(b))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer r.Close()\n\t\tb2, err := io.ReadAll(r)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb = b2\n\t}\n\n\t// extract objects from Helm storage\n\tvar rls hrStorage\n\tif err := json.Unmarshal(b, &rls); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode the Helm storage object for HelmRelease '%s': %w\", objectKey.String(), err)\n\t}\n\n\tobjects, err := ssautil.ReadObjects(strings.NewReader(rls.Manifest))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read the Helm storage object for HelmRelease '%s': %w\", objectKey.String(), err)\n\t}\n\n\t// set the namespace on namespaced objects\n\tfor _, obj := range objects {\n\t\tif obj.GetNamespace() == \"\" {\n\t\t\tif isNamespaced, _ := apiutil.IsObjectNamespaced(obj, kubeClient.Scheme(), kubeClient.RESTMapper()); isNamespaced {\n\t\t\t\tobj.SetNamespace(latest.Namespace)\n\t\t\t}\n\t\t}\n\t}\n\n\tresult := object.UnstructuredSetToObjMetadataSet(objects)\n\n\t// search for CRDs managed by the HelmRelease if installing or upgrading CRDs is enabled in spec\n\tif (hr.Spec.Install != nil && len(hr.Spec.Install.CRDs) > 0 && hr.Spec.Install.CRDs != helmv2.Skip) ||\n\t\t(hr.Spec.Upgrade != nil && len(hr.Spec.Upgrade.CRDs) > 0 && hr.Spec.Upgrade.CRDs != helmv2.Skip) {\n\t\tselector := client.MatchingLabels{\n\t\t\tfmt.Sprintf(\"%s/name\", helmv2.GroupVersion.Group):      hr.GetName(),\n\t\t\tfmt.Sprintf(\"%s/namespace\", helmv2.GroupVersion.Group): hr.GetNamespace(),\n\t\t}\n\t\tcrdKind := \"CustomResourceDefinition\"\n\t\tvar list apiextensionsv1.CustomResourceDefinitionList\n\t\tif err := kubeClient.List(ctx, &list, selector); err == nil {\n\t\t\tfor _, crd := range list.Items {\n\t\t\t\tfound := false\n\t\t\t\tfor _, r := range result {\n\t\t\t\t\tif r.Name == crd.GetName() && r.GroupKind.Kind == crdKind {\n\t\t\t\t\t\tfound = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif !found {\n\t\t\t\t\tresult = append(result, object.ObjMetadata{\n\t\t\t\t\t\tName: crd.GetName(),\n\t\t\t\t\t\tGroupKind: schema.GroupKind{\n\t\t\t\t\t\t\tGroup: apiextensionsv1.GroupName,\n\t\t\t\t\t\t\tKind:  crdKind,\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\treturn result, nil\n}\n"
  },
  {
    "path": "cmd/flux/tree_kustomization_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestTree(t *testing.T) {\n\tcases := []struct {\n\t\tname       string\n\t\targs       string\n\t\tobjectFile string\n\t\tgoldenFile string\n\t}{\n\t\t{\n\t\t\t\"tree kustomization\",\n\t\t\t\"tree kustomization flux-system\",\n\t\t\t\"testdata/tree/kustomizations.yaml\",\n\t\t\t\"testdata/tree/tree.golden\",\n\t\t},\n\t\t{\n\t\t\t\"tree kustomization compact\",\n\t\t\t\"tree kustomization flux-system --compact\",\n\t\t\t\"testdata/tree/kustomizations.yaml\",\n\t\t\t\"testdata/tree/tree-compact.golden\",\n\t\t},\n\t\t{\n\t\t\t\"tree kustomization empty\",\n\t\t\t\"tree kustomization empty\",\n\t\t\t\"testdata/tree/kustomizations.yaml\",\n\t\t\t\"testdata/tree/tree-empty.golden\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmpl := map[string]string{\n\t\t\t\t\"fluxns\": allocateNamespace(\"flux-system\"),\n\t\t\t}\n\t\t\ttestEnv.CreateObjectFile(tc.objectFile, tmpl, t)\n\t\t\tcmd := cmdTestCase{\n\t\t\t\targs:   tc.args + \" -n=\" + tmpl[\"fluxns\"],\n\t\t\t\tassert: assertGoldenTemplateFile(tc.goldenFile, tmpl),\n\t\t\t}\n\t\t\tcmd.runTestCmd(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/uninstall.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/manifoldco/promptui\"\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/uninstall\"\n)\n\nvar uninstallCmd = &cobra.Command{\n\tUse:   \"uninstall\",\n\tArgs:  cobra.NoArgs,\n\tShort: \"Uninstall Flux and its custom resource definitions\",\n\tLong:  `The uninstall command removes the Flux components and the toolkit.fluxcd.io resources from the cluster.`,\n\tExample: `  # Uninstall Flux components, its custom resources and namespace\n  flux uninstall --namespace=flux-system\n\n  # Uninstall Flux but keep the namespace\n  flux uninstall --namespace=infra --keep-namespace=true`,\n\tRunE: uninstallCmdRun,\n}\n\ntype uninstallFlags struct {\n\tkeepNamespace bool\n\tdryRun        bool\n\tsilent        bool\n}\n\nvar uninstallArgs uninstallFlags\n\nfunc init() {\n\tuninstallCmd.Flags().BoolVar(&uninstallArgs.keepNamespace, \"keep-namespace\", false,\n\t\t\"skip namespace deletion\")\n\tuninstallCmd.Flags().BoolVar(&uninstallArgs.dryRun, \"dry-run\", false,\n\t\t\"only print the objects that would be deleted\")\n\tuninstallCmd.Flags().BoolVarP(&uninstallArgs.silent, \"silent\", \"s\", false,\n\t\t\"delete components without asking for confirmation\")\n\n\trootCmd.AddCommand(uninstallCmd)\n}\n\nfunc uninstallCmdRun(cmd *cobra.Command, args []string) error {\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !uninstallArgs.dryRun && !uninstallArgs.silent {\n\t\tinfo, err := getFluxClusterInfo(ctx, kubeClient)\n\t\tif err != nil {\n\t\t\tif !errors.IsNotFound(err) {\n\t\t\t\treturn fmt.Errorf(\"cluster info unavailable: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\tpromptLabel := \"Are you sure you want to delete Flux and its custom resource definitions\"\n\t\tif !installManagedByFlux(info.managedBy) {\n\t\t\tpromptLabel = fmt.Sprintf(\"Flux is managed by %s! Are you sure you want to delete Flux and its CRDs using Flux CLI\", info.managedBy)\n\t\t}\n\t\tprompt := promptui.Prompt{\n\t\t\tLabel:     promptLabel,\n\t\t\tIsConfirm: true,\n\t\t}\n\t\tif _, err := prompt.Run(); err != nil {\n\t\t\treturn fmt.Errorf(\"aborting\")\n\t\t}\n\t}\n\n\tlogger.Actionf(\"deleting components in %s namespace\", *kubeconfigArgs.Namespace)\n\tuninstall.Components(ctx, logger, kubeClient, *kubeconfigArgs.Namespace, uninstallArgs.dryRun)\n\n\tlogger.Actionf(\"deleting toolkit.fluxcd.io finalizers in all namespaces\")\n\tuninstall.Finalizers(ctx, logger, kubeClient, uninstallArgs.dryRun)\n\n\tlogger.Actionf(\"deleting toolkit.fluxcd.io custom resource definitions\")\n\tuninstall.CustomResourceDefinitions(ctx, logger, kubeClient, uninstallArgs.dryRun)\n\n\tif !uninstallArgs.keepNamespace {\n\t\tuninstall.Namespace(ctx, logger, kubeClient, *kubeconfigArgs.Namespace, uninstallArgs.dryRun)\n\t}\n\n\tlogger.Successf(\"uninstall finished\")\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/flux/version.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/google/go-containerregistry/pkg/name\"\n\t\"github.com/spf13/cobra\"\n\tv1 \"k8s.io/api/apps/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/yaml/goyaml.v2\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n)\n\nvar versionCmd = &cobra.Command{\n\tUse:   \"version\",\n\tShort: \"Print the client and server-side components version information.\",\n\tLong:  `Print the client and server-side components version information for the current context.`,\n\tExample: `# Print client and server-side version \n\tflux version\n\n\t# Print only client version\n\tflux version --client\n\n\t# Print information in json format\n\tflux version -o json\n`,\n\tRunE: versionCmdRun,\n}\n\ntype versionFlags struct {\n\tclient bool\n\toutput string\n}\n\nvar versionArgs versionFlags\n\ntype versionInfo struct {\n\tFlux         string            `yaml:\"flux\"`\n\tDistribution string            `yaml:\"distribution,omitempty\"`\n\tController   map[string]string `yaml:\"controller,inline\"`\n}\n\nfunc init() {\n\tversionCmd.Flags().BoolVar(&versionArgs.client, \"client\", false,\n\t\t\"print only client version\")\n\tversionCmd.Flags().StringVarP(&versionArgs.output, \"output\", \"o\", \"yaml\",\n\t\t\"the format in which the information should be printed. can be 'json' or 'yaml'\")\n\trootCmd.AddCommand(versionCmd)\n}\n\nfunc versionCmdRun(cmd *cobra.Command, args []string) error {\n\tif versionArgs.output != \"yaml\" && versionArgs.output != \"json\" {\n\t\treturn fmt.Errorf(\"--output must be json or yaml, not %s\", versionArgs.output)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)\n\tdefer cancel()\n\n\t// versionInfo struct and goyaml is used because we care about the order.\n\t// Without this `distribution` is printed before `flux` when the struct is marshalled.\n\tinfo := &versionInfo{\n\t\tController: map[string]string{},\n\t}\n\tinfo.Flux = rootArgs.defaults.Version\n\n\tif !versionArgs.client {\n\t\tkubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tclusterInfo, err := getFluxClusterInfo(ctx, kubeClient)\n\t\t// ignoring not found errors because it means that the GitRepository CRD isn't installed but a user might\n\t\t// have other controllers(e.g notification-controller), and  we want to still return information for them.\n\t\tif err != nil && !errors.IsNotFound(err) {\n\t\t\treturn err\n\t\t}\n\t\tif clusterInfo.distribution() != \"\" {\n\t\t\tinfo.Distribution = clusterInfo.distribution()\n\t\t}\n\n\t\tselector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}\n\t\tvar list v1.DeploymentList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(*kubeconfigArgs.Namespace), selector); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif len(list.Items) == 0 {\n\t\t\treturn fmt.Errorf(\"no deployments found in %s namespace\", *kubeconfigArgs.Namespace)\n\t\t}\n\n\t\tfor _, d := range list.Items {\n\t\t\tfor _, c := range d.Spec.Template.Spec.Containers {\n\t\t\t\tname, tag, err := splitImageStr(c.Image)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tinfo.Controller[name] = tag\n\t\t\t}\n\t\t}\n\t}\n\n\tvar marshalled []byte\n\tvar err error\n\n\tif versionArgs.output == \"json\" {\n\t\tmarshalled, err = info.toJSON()\n\t\tmarshalled = append(marshalled, \"\\n\"...)\n\t} else {\n\t\tmarshalled, err = yaml.Marshal(&info)\n\t}\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trootCmd.Print(string(marshalled))\n\treturn nil\n}\n\nfunc (info versionInfo) toJSON() ([]byte, error) {\n\tmapInfo := map[string]string{\n\t\t\"flux\": info.Flux,\n\t}\n\n\tif info.Distribution != \"\" {\n\t\tmapInfo[\"distribution\"] = info.Distribution\n\t}\n\tfor k, v := range info.Controller {\n\t\tmapInfo[k] = v\n\t}\n\treturn json.MarshalIndent(&mapInfo, \"\", \"  \")\n}\n\nfunc splitImageStr(image string) (string, string, error) {\n\tref, err := name.ParseReference(image)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"parsing image '%s' failed: %w\", image, err)\n\t}\n\n\treg := ref.Context().RegistryStr()\n\trepo := strings.TrimPrefix(image, reg)\n\tparts := strings.Split(repo, \":\")\n\tif len(parts) < 2 {\n\t\treturn \"\", \"\", fmt.Errorf(\"missing image tag in image %s\", image)\n\t}\n\n\tn, t := parts[0], strings.TrimPrefix(repo, parts[0]+\":\")\n\tnameArr := strings.Split(n, \"/\")\n\treturn nameArr[len(nameArr)-1], t, nil\n}\n"
  },
  {
    "path": "cmd/flux/version_test.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestSplitImageStr(t *testing.T) {\n\ttests := []struct {\n\t\turl          string\n\t\texpectedName string\n\t\texpectedTag  string\n\t}{\n\t\t{\n\t\t\turl:          \"fluxcd/notification-controller:v1.0.0\",\n\t\t\texpectedName: \"notification-controller\",\n\t\t\texpectedTag:  \"v1.0.0\",\n\t\t},\n\t\t{\n\t\t\turl:          \"ghcr.io/fluxcd/kustomize-controller:v1.0.0\",\n\t\t\texpectedName: \"kustomize-controller\",\n\t\t\texpectedTag:  \"v1.0.0\",\n\t\t},\n\t\t{\n\t\t\turl:          \"reg.internal:8080/fluxcd/source-controller:v1.0.0\",\n\t\t\texpectedName: \"source-controller\",\n\t\t\texpectedTag:  \"v1.0.0\",\n\t\t},\n\t\t{\n\t\t\turl:          \"fluxcd/source-controller:v1.0.1@sha256:49921d1c7b100650dd654a32df1f6e626b54dfe9707d7bb7bdf43fb7c81f1baf\",\n\t\t\texpectedName: \"source-controller\",\n\t\t\texpectedTag:  \"v1.0.1@sha256:49921d1c7b100650dd654a32df1f6e626b54dfe9707d7bb7bdf43fb7c81f1baf\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tg := NewWithT(t)\n\t\tn, t, err := splitImageStr(tt.url)\n\t\tg.Expect(err).To(Not(HaveOccurred()))\n\t\tg.Expect(n).To(BeEquivalentTo(tt.expectedName))\n\t\tg.Expect(t).To(BeEquivalentTo(tt.expectedTag))\n\t}\n}\n"
  },
  {
    "path": "cmd/flux/version_utils.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n)\n\nfunc getVersion(input string) (string, error) {\n\tif input == \"\" {\n\t\treturn rootArgs.defaults.Version, nil\n\t}\n\n\tif input != install.MakeDefaultOptions().Version && !strings.HasPrefix(input, \"v\") {\n\t\treturn \"\", fmt.Errorf(\"targeted version '%s' must be prefixed with 'v'\", input)\n\t}\n\n\tif isEmbeddedVersion(input) {\n\t\treturn input, nil\n\t}\n\n\tvar err error\n\tif input == install.MakeDefaultOptions().Version {\n\t\tinput, err = install.GetLatestVersion()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t} else {\n\t\tif ok, err := install.ExistingVersion(input); err != nil || !ok {\n\t\t\tif err == nil {\n\t\t\t\terr = fmt.Errorf(\"targeted version '%s' does not exist\", input)\n\t\t\t}\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\n\tif !utils.CompatibleVersion(VERSION, input) {\n\t\treturn \"\", fmt.Errorf(\"targeted version '%s' is not compatible with your current version of flux (%s)\", input, VERSION)\n\t}\n\treturn input, nil\n}\n\nfunc isEmbeddedVersion(input string) bool {\n\treturn input == rootArgs.defaults.Version\n}\n"
  },
  {
    "path": "cmd/flux/version_utils_test.go",
    "content": "//go:build unit\n// +build unit\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestVersion(t *testing.T) {\n\tcmd := cmdTestCase{\n\t\targs:   \"--version\",\n\t\tassert: assertGoldenValue(\"flux version 0.0.0-dev.0\\n\"),\n\t}\n\tcmd.runTestCmd(t)\n}\n\nfunc TestVersionCmd(t *testing.T) {\n\ttests := []struct {\n\t\targs     string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\targs:     \"version --client\",\n\t\t\texpected: \"flux: v0.0.0-dev.0\\n\",\n\t\t},\n\t\t{\n\t\t\targs:     \"version --client -o json\",\n\t\t\texpected: \"{\\n  \\\"flux\\\": \\\"v0.0.0-dev.0\\\"\\n}\\n\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tcmd := cmdTestCase{\n\t\t\targs:   tt.args,\n\t\t\tassert: assertGoldenValue(tt.expected),\n\t\t}\n\t\tcmd.runTestCmd(t)\n\t}\n}\n"
  },
  {
    "path": "docs/release/README.md",
    "content": "# Flux Dev Documentation\n\n## Release specifications\n\n- [Flux distribution](https://fluxcd.io/flux/releases/)\n- [Flux APIs and controllers](https://fluxcd.io/flux/releases/controllers/)\n- [Flux shared packages](https://fluxcd.io/flux/releases/packages/)\n- [Flux release procedures](https://fluxcd.io/flux/releases/procedure/)\n- [Flux release notes template](release-notes-template.md)\n"
  },
  {
    "path": "docs/release/release-notes-template.md",
    "content": "# Flux release note template\n\nThis is a template for release notes. It is intended to be used as a\nstarting point for writing release notes for a new release. It should be copied\nto a temporary file, and then edited to reflect the changes in the release.\n\nOnce the release notes are complete, you can tag the release and push it to\nGitHub.\n\nAfter the release is tagged, the CI will build the release artifacts and upload\nthem to the GitHub release page. The release notes can then be copied from the\ntemporary file to the GitHub release page.\n\nThe release notes should be formatted using [Markdown](https://guides.github.com/features/mastering-markdown/),\nand not make use of line breaks unless they function as paragraph breaks.\n\nFor examples of release notes, including language and formatting of the release\nhighlights, see the [Flux release notes](https://github.com/fluxcd/flux2/releases).\n\n## GitHub release template\n\nThe following template can be used for the GitHub release page:\n\n```markdown\n## Highlights\n\n<!-- Text describing the most important changes in this release -->\n\nℹ️ Please follow the [Upgrade Procedure for Flux v2.7+](https://github.com/fluxcd/flux2/discussions/5572) for a smooth upgrade from Flux v2.6 to the latest version.\n\n### Fixes and improvements\n\n<!-- List of fixes and improvements to the controllers and CLI -->\n\n## New documentation\n\n<!-- List of new documentation pages, if applicable -->\n\n## Components changelog\n\n- <name>-controller [v<version>](https://github.com/fluxcd/<name>-controller/blob/<version>/CHANGELOG.md)\n\n## CLI changelog\n\n<!-- auto-generated list of pull requests to the CLI starts here -->\n```\n\nTypically, you want to link the [Flux upgrade](https://fluxcd.io/flux/installation/upgrade/)\nguide to refer users for up to date information on upgrade options.\n\nIn some scenarios, you may want to include specific information about API\nchanges and/or upgrade procedures. Consult [the formatting of\n`v2.0.0-rc.1`](https://github.com/fluxcd/flux2/releases/tag/v2.0.0-rc.1) for\nsuch an example.\n\n## Slack message template\n\nThe following template can be used for the Slack release message:\n\n```markdown\n:sparkles: *We are pleased to announce the release of Flux [<version>](https://github.com/fluxcd/flux2/releases/tag/<version>/)!*\n\n<!-- Brief description of most important changes to this release -->\n\n:hammer_and_pick: *Fixes and improvements*\n\n<!-- List of fixes and improvements as in the GitHub release template -->\n\n:books: Documentation\n\n<!-- List of new documentation pages, if applicable -->\n\n:heart: Big thanks to all the Flux contributors that helped us with this release! \n```\n\nFor more concrete examples, see the pinned messages in the [Flux Slack\nchannel](https://cloud-native.slack.com/archives/CLAJ40HV3).\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/fluxcd/flux2/v2\n\ngo 1.26.0\n\n// Fix CVE-2022-28948.\nreplace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1\n\nrequire (\n\tgithub.com/Masterminds/semver/v3 v3.4.0\n\tgithub.com/ProtonMail/go-crypto v1.3.0\n\tgithub.com/cyphar/filepath-securejoin v0.6.1\n\tgithub.com/distribution/distribution/v3 v3.0.0\n\tgithub.com/fluxcd/cli-utils v0.37.2-flux.1\n\tgithub.com/fluxcd/go-git-providers v0.26.0\n\tgithub.com/fluxcd/helm-controller/api v1.5.3\n\tgithub.com/fluxcd/image-automation-controller/api v1.1.1\n\tgithub.com/fluxcd/image-reflector-controller/api v1.1.1\n\tgithub.com/fluxcd/kustomize-controller/api v1.8.2\n\tgithub.com/fluxcd/notification-controller/api v1.8.2\n\tgithub.com/fluxcd/pkg/apis/event v0.25.0\n\tgithub.com/fluxcd/pkg/apis/meta v1.26.0\n\tgithub.com/fluxcd/pkg/auth v0.40.0\n\tgithub.com/fluxcd/pkg/chartutil v1.23.0\n\tgithub.com/fluxcd/pkg/envsubst v1.5.0\n\tgithub.com/fluxcd/pkg/git v0.46.0\n\tgithub.com/fluxcd/pkg/kustomize v1.28.0\n\tgithub.com/fluxcd/pkg/oci v0.63.0\n\tgithub.com/fluxcd/pkg/runtime v0.103.0\n\tgithub.com/fluxcd/pkg/sourceignore v0.17.0\n\tgithub.com/fluxcd/pkg/ssa v0.70.0\n\tgithub.com/fluxcd/pkg/ssh v0.24.0\n\tgithub.com/fluxcd/pkg/tar v0.17.0\n\tgithub.com/fluxcd/pkg/version v0.14.0\n\tgithub.com/fluxcd/source-controller/api v1.8.1\n\tgithub.com/fluxcd/source-watcher/api/v2 v2.1.1\n\tgithub.com/go-git/go-git/v5 v5.16.5\n\tgithub.com/go-logr/logr v1.4.3\n\tgithub.com/gonvenience/bunt v1.4.2\n\tgithub.com/gonvenience/ytbx v1.4.7\n\tgithub.com/google/go-cmp v0.7.0\n\tgithub.com/google/go-containerregistry v0.20.7\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2\n\tgithub.com/homeport/dyff v1.10.2\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0\n\tgithub.com/manifoldco/promptui v0.9.0\n\tgithub.com/mattn/go-shellwords v1.0.12\n\tgithub.com/notaryproject/notation-go v1.3.2\n\tgithub.com/olekukonko/tablewriter v0.0.5\n\tgithub.com/onsi/gomega v1.39.1\n\tgithub.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5\n\tgithub.com/spf13/cobra v1.10.2\n\tgithub.com/theckman/yacspin v0.13.12\n\tgolang.org/x/crypto v0.48.0\n\tgolang.org/x/term v0.40.0\n\tgolang.org/x/text v0.34.0\n\tk8s.io/api v0.35.2\n\tk8s.io/apiextensions-apiserver v0.35.2\n\tk8s.io/apimachinery v0.35.2\n\tk8s.io/cli-runtime v0.35.2\n\tk8s.io/client-go v0.35.2\n\tk8s.io/kubectl v0.35.2\n\tsigs.k8s.io/controller-runtime v0.23.3\n\tsigs.k8s.io/kustomize/api v0.21.1\n\tsigs.k8s.io/kustomize/kyaml v0.21.1\n\tsigs.k8s.io/yaml v1.6.0\n)\n\nrequire (\n\tcloud.google.com/go/auth v0.18.0 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/compute/metadata v0.9.0 // indirect\n\tcode.gitea.io/sdk/gitea v0.23.2 // indirect\n\tdario.cat/mergo v1.0.1 // indirect\n\tgithub.com/42wim/httpsig v1.2.3 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 // indirect\n\tgithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect\n\tgithub.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect\n\tgithub.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect\n\tgithub.com/BurntSushi/toml v1.6.0 // indirect\n\tgithub.com/MakeNowJust/heredoc v1.0.0 // indirect\n\tgithub.com/Microsoft/go-winio v0.6.2 // indirect\n\tgithub.com/aws/aws-sdk-go-v2 v1.41.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/config v1.32.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.19.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ecr v1.55.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.9 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/eks v1.77.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect\n\tgithub.com/aws/smithy-go v1.24.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect\n\tgithub.com/cenkalti/backoff/v5 v5.0.3 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/chai2010/gettext-go v1.0.2 // indirect\n\tgithub.com/chzyer/readline v1.5.1 // indirect\n\tgithub.com/cloudflare/circl v1.6.3 // indirect\n\tgithub.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect\n\tgithub.com/coreos/go-systemd/v22 v22.5.0 // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/davidmz/go-pageant v1.0.2 // indirect\n\tgithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect\n\tgithub.com/distribution/reference v0.6.0 // indirect\n\tgithub.com/docker/cli v29.2.0+incompatible // indirect\n\tgithub.com/docker/distribution v2.8.3+incompatible // indirect\n\tgithub.com/docker/docker-credential-helpers v0.9.3 // indirect\n\tgithub.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect\n\tgithub.com/docker/go-metrics v0.0.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.12.2 // indirect\n\tgithub.com/emirpasic/gods v1.18.1 // indirect\n\tgithub.com/evanphx/json-patch v5.9.11+incompatible // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.9.11 // indirect\n\tgithub.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect\n\tgithub.com/fatih/color v1.18.0 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fluxcd/pkg/apis/acl v0.9.0 // indirect\n\tgithub.com/fluxcd/pkg/apis/kustomize v1.16.0 // indirect\n\tgithub.com/fluxcd/pkg/cache v0.13.0 // indirect\n\tgithub.com/fsnotify/fsnotify v1.9.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-asn1-ber/asn1-ber v1.5.7 // indirect\n\tgithub.com/go-errors/errors v1.5.1 // indirect\n\tgithub.com/go-fed/httpsig v1.1.0 // indirect\n\tgithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect\n\tgithub.com/go-git/go-billy/v5 v5.7.0 // indirect\n\tgithub.com/go-ldap/ldap/v3 v3.4.10 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.1 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.0 // indirect\n\tgithub.com/go-openapi/swag v0.23.1 // indirect\n\tgithub.com/golang-jwt/jwt/v5 v5.3.0 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect\n\tgithub.com/gonvenience/idem v0.0.2 // indirect\n\tgithub.com/gonvenience/neat v1.3.16 // indirect\n\tgithub.com/gonvenience/term v1.0.4 // indirect\n\tgithub.com/gonvenience/text v1.0.9 // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/gnostic-models v0.7.0 // indirect\n\tgithub.com/google/go-github/v82 v82.0.0 // indirect\n\tgithub.com/google/go-querystring v1.2.0 // 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.11 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.16.0 // indirect\n\tgithub.com/gorilla/handlers v1.5.2 // indirect\n\tgithub.com/gorilla/mux v1.8.1 // indirect\n\tgithub.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/go-retryablehttp v0.7.8 // indirect\n\tgithub.com/hashicorp/go-version v1.7.0 // indirect\n\tgithub.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.5 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/kevinburke/ssh_config v1.4.0 // indirect\n\tgithub.com/klauspost/compress v1.18.1 // indirect\n\tgithub.com/kylelemons/godebug v1.1.0 // indirect\n\tgithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // 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.20 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.16 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/mitchellh/go-ps v1.0.0 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1 // indirect\n\tgithub.com/mitchellh/hashstructure v1.1.0 // indirect\n\tgithub.com/moby/term v0.5.2 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/notaryproject/notation-core-go v1.3.0 // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.1 // indirect\n\tgithub.com/peterbourgon/diskv v2.0.1+incompatible // indirect\n\tgithub.com/pjbgf/sha1cd v0.4.0 // indirect\n\tgithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // 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/prometheus/client_golang v1.23.2 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.67.5 // indirect\n\tgithub.com/prometheus/otlptranslator v1.0.0 // indirect\n\tgithub.com/prometheus/procfs v0.19.2 // indirect\n\tgithub.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect\n\tgithub.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect\n\tgithub.com/redis/go-redis/v9 v9.7.3 // indirect\n\tgithub.com/rivo/uniseg v0.2.0 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/sergi/go-diff v1.4.0 // indirect\n\tgithub.com/sirupsen/logrus v1.9.4 // indirect\n\tgithub.com/skeema/knownhosts v1.3.1 // indirect\n\tgithub.com/spf13/pflag v1.0.10 // indirect\n\tgithub.com/texttheater/golang-levenshtein v1.0.1 // indirect\n\tgithub.com/vbatts/tar-split v0.12.2 // 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/xanzy/ssh-agent v0.3.3 // indirect\n\tgithub.com/xlab/treeprint v1.2.0 // indirect\n\tgitlab.com/gitlab-org/api/client-go v1.29.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/bridges/prometheus v0.65.0 // indirect\n\tgo.opentelemetry.io/contrib/exporters/autoexport v0.65.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect\n\tgo.opentelemetry.io/otel v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/prometheus v0.62.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/log v0.16.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/log v0.16.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.40.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.9.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/net v0.50.0 // indirect\n\tgolang.org/x/oauth2 v0.35.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.5.0 // indirect\n\tgoogle.golang.org/api v0.261.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect\n\tgoogle.golang.org/grpc v1.78.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.11 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/warnings.v0 v0.1.2 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\thelm.sh/helm/v4 v4.1.3 // indirect\n\tk8s.io/component-base v0.35.2 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect\n\tk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=\ncloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=\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=\ncode.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg=\ncode.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM=\ndario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=\ndario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=\ngithub.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=\ngithub.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=\ngithub.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 h1:ldKsKtEIblsgsr6mPwrd9yRntoX6uLz/K89wsldwx/k=\ngithub.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3/go.mod h1:MAm7bk0oDLmD8yIkvfbxPW04fxzphPyL+7GzwHxOp6Y=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 h1:figxyQZXzZQIcP3njhC68bYUiTw45J8/SsHaLW8Ax0M=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0/go.mod h1:TmlMW4W5OvXOmOyKNnor8nlMMiO1ctIyzmHme/VHsrA=\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/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=\ngithub.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=\ngithub.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=\ngithub.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=\ngithub.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=\ngithub.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=\ngithub.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=\ngithub.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=\ngithub.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=\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-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU=\ngithub.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=\ngithub.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY=\ngithub.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=\ngithub.com/aws/aws-sdk-go-v2/service/ecr v1.55.1 h1:B7f9R99lCF83XlolTg6d6Lvghyto+/VU83ZrneAVfK8=\ngithub.com/aws/aws-sdk-go-v2/service/ecr v1.55.1/go.mod h1:cpYRXx5BkmS3mwWRKPbWSPKmyAUNL7aLWAPiiinwk/U=\ngithub.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.9 h1:WxoqdNfGWj668u/NX7qBMPevmJu14LYNMMTRZthoclc=\ngithub.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.9/go.mod h1:4oMS/bVKMnYIIBgkcHPoru4DVeMGutHv03FZUTjvsvI=\ngithub.com/aws/aws-sdk-go-v2/service/eks v1.77.0 h1:Z5mTpmbJKU7jEM7xoXI5tO4Nm0JUZSgVSFkpYuu6Ic0=\ngithub.com/aws/aws-sdk-go-v2/service/eks v1.77.0/go.mod h1:Qg678m+87sCuJhcsZojenz8mblYG+Tq86V4m3hjVz0s=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU=\ngithub.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y=\ngithub.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ=\ngithub.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=\ngithub.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=\ngithub.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=\ngithub.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=\ngithub.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=\ngithub.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=\ngithub.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=\ngithub.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=\ngithub.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=\ngithub.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=\ngithub.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=\ngithub.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=\ngithub.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\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/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=\ngithub.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=\ngithub.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=\ngithub.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=\ngithub.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=\ngithub.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=\ngithub.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=\ngithub.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8=\ngithub.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q=\ngithub.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=\ngithub.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=\ngithub.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=\ngithub.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=\ngithub.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=\ngithub.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=\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/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=\ngithub.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=\ngithub.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM=\ngithub.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU=\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 v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=\ngithub.com/docker/cli v29.2.0+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-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-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=\ngithub.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=\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/elazarl/goproxy v1.8.0 h1:dt561rX7UAYMeFRLtzFx6uQGl2TpL1dr6uCG23nFQSY=\ngithub.com/elazarl/goproxy v1.8.0/go.mod h1:b5xm6W48AUHNpRTCvlnd0YVh+JafCCtsLsJZvvNTz+E=\ngithub.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=\ngithub.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=\ngithub.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=\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/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=\ngithub.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=\ngithub.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=\ngithub.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=\ngithub.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fluxcd/cli-utils v0.37.2-flux.1 h1:tQ588ghtRN+E+kHq415FddfqA9v4brn/1WWgrP6rQR0=\ngithub.com/fluxcd/cli-utils v0.37.2-flux.1/go.mod h1:LcWSu1NYET8d8U7O326RhEm5JkQXCMK6ITu4G1CT02c=\ngithub.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg=\ngithub.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo=\ngithub.com/fluxcd/go-git-providers v0.26.0 h1:0DUsXc1nS9Fe4n8tXSEUCGemWzHShd66gmotayDPekw=\ngithub.com/fluxcd/go-git-providers v0.26.0/go.mod h1:VJDKUOhZwNAIqDF5iPtIpTr/annsDbKMkPpWiDMBdpo=\ngithub.com/fluxcd/helm-controller/api v1.5.3 h1:ruLzuyTHjjE9A5B/U+Id2q7yHXXqSFTswdZ14xCS5So=\ngithub.com/fluxcd/helm-controller/api v1.5.3/go.mod h1:lTgeUmtVYExMKp7mRDncsr4JwHTz3LFtLjRJZeR98lI=\ngithub.com/fluxcd/image-automation-controller/api v1.1.1 h1:uiu7kjdVoW8/461HOemX6I7RcPornEzQliWgTg6LnWI=\ngithub.com/fluxcd/image-automation-controller/api v1.1.1/go.mod h1:lkD/drkD6Wc+2SDjVj5KqfozEucTLFexWgby/5ft660=\ngithub.com/fluxcd/image-reflector-controller/api v1.1.1 h1:4Bj1abzVnjj8+b/293kNeFMRJc+y2wO8Z12ReZ/gA0w=\ngithub.com/fluxcd/image-reflector-controller/api v1.1.1/go.mod h1:j4JSIocL42HQ77Veg1t60sApOy+lng8/cbXHXGSnfi0=\ngithub.com/fluxcd/kustomize-controller/api v1.8.2 h1:LcFUjJccwNrhCo7pQBBneLAlHfZZcb58bWB2LnyFwag=\ngithub.com/fluxcd/kustomize-controller/api v1.8.2/go.mod h1:c/mUPIffDDLg1EicXCJtX4N/rc+z5Zh0e/CXjhd7Dyc=\ngithub.com/fluxcd/notification-controller/api v1.8.2 h1:TDrXohUC5Gh3BF+v2ux9/zEG1Ax8u49WDW+3Y6GiIEc=\ngithub.com/fluxcd/notification-controller/api v1.8.2/go.mod h1:ozgJGQPy0dG5eOsLZlwAr6n0q/y6+TWd1fGOtavlXJA=\ngithub.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA=\ngithub.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4=\ngithub.com/fluxcd/pkg/apis/event v0.25.0 h1:zdwytvDhG+fk+Ywl5DOtv7TklkrVgM21WHm1f+YhleE=\ngithub.com/fluxcd/pkg/apis/event v0.25.0/go.mod h1:TlK8HWYrTwl0raqBRC+ROoNpYW5fdVnwcwOBOx5Kzw8=\ngithub.com/fluxcd/pkg/apis/kustomize v1.16.0 h1:PhWXEhqQqsisIpwp1/wHvTvo+MO+GGzsBPoN0ZnRE3Y=\ngithub.com/fluxcd/pkg/apis/kustomize v1.16.0/go.mod h1:IZOy4CCtR/hxMGb7erK1RfbGnczVv4/dRBoVD37AywI=\ngithub.com/fluxcd/pkg/apis/meta v1.26.0 h1:dxP1FfBpTCYso6odzRcltVnnRuBb2VyhhgV0VX9YbUE=\ngithub.com/fluxcd/pkg/apis/meta v1.26.0/go.mod h1:c7o6mJGLCMvNrfdinGZehkrdZuFT9vZdZNrn66DtVD0=\ngithub.com/fluxcd/pkg/auth v0.40.0 h1:p6Kw6KH+z8oRqngKhmTt8ILKD/rC+8tP87a//kLZhi8=\ngithub.com/fluxcd/pkg/auth v0.40.0/go.mod h1:Oq/hIEKUMTbL2bv5blf+EhC/jXXJLsOjIMtJj/AtG3Y=\ngithub.com/fluxcd/pkg/cache v0.13.0 h1:MqtlgOwIVcGKKgV422e39O+KFSVMWuExKeRaMDBjJlk=\ngithub.com/fluxcd/pkg/cache v0.13.0/go.mod h1:0xRZ1hitrIFQ6pl68ke2wZLbIqA2VLzY78HpDo9DVxs=\ngithub.com/fluxcd/pkg/chartutil v1.23.0 h1:ohstQEVnrBIbN85FGu83hnmAohLl0PdOoPlsM6+cjyI=\ngithub.com/fluxcd/pkg/chartutil v1.23.0/go.mod h1:kFhmD6DwBgRsvC1ilINsomargMi2WbqvSndWQLikkLc=\ngithub.com/fluxcd/pkg/envsubst v1.5.0 h1:S07mo+MkGhptdHA4pRze5HPKlc8tHxKswNdcMZi1WDY=\ngithub.com/fluxcd/pkg/envsubst v1.5.0/go.mod h1:c3a8DYI855sZUubHFYQbjfjop6Wu4/zg1cLyf7SnCes=\ngithub.com/fluxcd/pkg/git v0.46.0 h1:QMh0+ZzQ2jO6rIGj4ffR5trZ8g/cxvt8cVajReJ8Iyw=\ngithub.com/fluxcd/pkg/git v0.46.0/go.mod h1:iHcIjx9c8zye3PQiajTJYxgOMRiy7WCs+hfLKDswpfI=\ngithub.com/fluxcd/pkg/gittestserver v0.26.0 h1:+RZrCzFRsE+d5WaqAoqaPCEgcgv/jZp6+f7DS0+Ynb8=\ngithub.com/fluxcd/pkg/gittestserver v0.26.0/go.mod h1:7fybYb0yej1fFNiF1ohs0Jr0XzyaZQ/cRh3AFEoCtuc=\ngithub.com/fluxcd/pkg/kustomize v1.28.0 h1:0RuFVczJRabbt8frHZ/ql8aqte6BOOKk274O09l6/hE=\ngithub.com/fluxcd/pkg/kustomize v1.28.0/go.mod h1:cW08mnngSP8MJYb6mDmMvxH8YjNATdiML0udb37dk+M=\ngithub.com/fluxcd/pkg/oci v0.63.0 h1:ZPKTT2C+gWYjhP63xC76iTPdYE9w3ABcsDq77uhAgwo=\ngithub.com/fluxcd/pkg/oci v0.63.0/go.mod h1:qMPz4njvm6hJzdyGSb8ydSqrapXxTQwJonxHIsdeXSQ=\ngithub.com/fluxcd/pkg/runtime v0.103.0 h1:J5y5GPhWdkyqIUBlaI1FP2N02TtZmsjbWhhZubuTSFk=\ngithub.com/fluxcd/pkg/runtime v0.103.0/go.mod h1:mbo2f3azo3yVQgm7XZGxQB6/2zvzQ5Wgtd8TjRRwwAw=\ngithub.com/fluxcd/pkg/sourceignore v0.17.0 h1:Z72nruRMhC15zIEpWoDrAcJcJ1El6QDnP/aRDfE4WOA=\ngithub.com/fluxcd/pkg/sourceignore v0.17.0/go.mod h1:3e/VmYLId0pI/H5sK7W9Ibif+j0Ahns9RxNjDMtTTfY=\ngithub.com/fluxcd/pkg/ssa v0.70.0 h1:IBylYPiTK1IEdCC2DvjKXIhwQcbd5VufXA9WS3zO+tE=\ngithub.com/fluxcd/pkg/ssa v0.70.0/go.mod h1:6igtlt7/zF+nNFQpa5ZAkkvtpL6o36NRU39/PqqC+Bg=\ngithub.com/fluxcd/pkg/ssh v0.24.0 h1:hrPlxs0hhXf32DRqs68VbsXs0XfQMphyRVIk0rYYJa4=\ngithub.com/fluxcd/pkg/ssh v0.24.0/go.mod h1:xWammEqalrpurpcMiixJRXtynRQtBEoqheyU5F/vWrg=\ngithub.com/fluxcd/pkg/tar v0.17.0 h1:uNxbFXy8ly8C7fJ8D7w3rjTNJFrb4Hp1aY/30XkfvxY=\ngithub.com/fluxcd/pkg/tar v0.17.0/go.mod h1:b1xyIRYDD0ket4SV5u0UXYv+ZdN/O/HmIO5jZQdHQls=\ngithub.com/fluxcd/pkg/version v0.14.0 h1:T3llSc8sUnsuFrW5ng2ePSfXwGXUKv0YG9QXf0ErhWw=\ngithub.com/fluxcd/pkg/version v0.14.0/go.mod h1:YHdg/78kzf+kCqS+SqSOiUxum5AjxlixiqwpX6AUZB8=\ngithub.com/fluxcd/source-controller/api v1.8.1 h1:49HiJF5mNEdZTwueQMRahTVts35B+xhN5CsuOAL9gQ0=\ngithub.com/fluxcd/source-controller/api v1.8.1/go.mod h1:HgZ6NSH1cyOE2jRoNwln1xEwr9ETvrLeiy1o4O04vQM=\ngithub.com/fluxcd/source-watcher/api/v2 v2.1.1 h1:1LfT50ty+78MKKbschAZl28QbVqIyjaNq17KmW5wPJI=\ngithub.com/fluxcd/source-watcher/api/v2 v2.1.1/go.mod h1:6M1BzBGQRoIuSenSQlfJHwMVVobFPiNPxXqfN0IILc4=\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.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=\ngithub.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=\ngithub.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=\ngithub.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=\ngithub.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=\ngithub.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=\ngithub.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=\ngithub.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=\ngithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=\ngithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=\ngithub.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=\ngithub.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=\ngithub.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=\ngithub.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=\ngithub.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=\ngithub.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=\ngithub.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=\ngithub.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU=\ngithub.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\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-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=\ngithub.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=\ngithub.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=\ngithub.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=\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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=\ngithub.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=\ngithub.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=\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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/gonvenience/bunt v1.4.2 h1:nTgkFZsw38SIJKABhLj8aXj2rqion9Zo1so/EBkbFBY=\ngithub.com/gonvenience/bunt v1.4.2/go.mod h1:WjyEO2rSYR+OLZg67Ucl+gjdXPs8GpFl63SCA02XDyI=\ngithub.com/gonvenience/idem v0.0.2 h1:jWHknjPfSbiWgYKre9wB2FhMgVLd1RWXCXzVq+7VIWg=\ngithub.com/gonvenience/idem v0.0.2/go.mod h1:0Xv1MpnNL40+dsyOxaJFa7L8ekeTRr63WaWXpiWLFFM=\ngithub.com/gonvenience/neat v1.3.16 h1:Vb0iCkSHGWaA+ry69RY3HpQ6Ooo6o/g2wjI80db8DjI=\ngithub.com/gonvenience/neat v1.3.16/go.mod h1:sLxdQNNluxbpROxTTHs3XBSJX8fwFX5toEULUy74ODA=\ngithub.com/gonvenience/term v1.0.4 h1:qkCGfmUtpzs9W4jWgNijaGF6dg3oSIh+kZCzT5cPNZY=\ngithub.com/gonvenience/term v1.0.4/go.mod h1:OzNdQC5NVBou9AifaHd1QG6EP8iDdpaT7GFm1bVgslg=\ngithub.com/gonvenience/text v1.0.9 h1:U29BxT3NZnNPcfiEnAwt6yHXe38fQs2Q+WTqs1X+atI=\ngithub.com/gonvenience/text v1.0.9/go.mod h1:JQF1ifXNRaa66jnPLqoITA+y8WATlG0eJzFC9ElJS3s=\ngithub.com/gonvenience/ytbx v1.4.7 h1:3wJ7EOfdv3Lg+h0mzKo7f8d1zMY1EJtVzzYrA3UhjHQ=\ngithub.com/gonvenience/ytbx v1.4.7/go.mod h1:ZmAU727eOTYeC4aUJuqyb9vogNAN7NiSKfw6Aoxbqys=\ngithub.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=\ngithub.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=\ngithub.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.6.0/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.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I=\ngithub.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM=\ngithub.com/google/go-github/v82 v82.0.0 h1:OH09ESON2QwKCUVMYmMcVu1IFKFoaZHwqYaUtr/MVfk=\ngithub.com/google/go-github/v82 v82.0.0/go.mod h1:hQ6Xo0VKfL8RZ7z1hSfB4fvISg0QqHOqe9BP0qo+WvM=\ngithub.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=\ngithub.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=\ngithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=\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.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.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8=\ngithub.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=\ngithub.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=\ngithub.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=\ngithub.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=\ngithub.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=\ngithub.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=\ngithub.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=\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-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\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-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=\ngithub.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=\ngithub.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\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/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw=\ngithub.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU=\ngithub.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=\ngithub.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/homeport/dyff v1.10.2 h1:XyB+D0KVwjbUFTZYIkvPtsImwkfh+ObH2CEdEHTqdr4=\ngithub.com/homeport/dyff v1.10.2/go.mod h1:0kIjL/JOGaXigzrLY6kcl5esSStbAa99r6GzEvr7lrs=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=\ngithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=\ngithub.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=\ngithub.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=\ngithub.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=\ngithub.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=\ngithub.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=\ngithub.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=\ngithub.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=\ngithub.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=\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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=\ngithub.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=\ngithub.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=\ngithub.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=\ngithub.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=\ngithub.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\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/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=\ngithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=\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/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=\ngithub.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=\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.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=\ngithub.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=\ngithub.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\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-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/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 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=\ngithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/notaryproject/notation-core-go v1.3.0 h1:mWJaw1QBpBxpjLSiKOjzbZvB+xh2Abzk14FHWQ+9Kfs=\ngithub.com/notaryproject/notation-core-go v1.3.0/go.mod h1:hzvEOit5lXfNATGNBT8UQRx2J6Fiw/dq/78TQL8aE64=\ngithub.com/notaryproject/notation-go v1.3.2 h1:4223iLXOHhEV7ZPzIUJEwwMkhlgzoYFCsMJvSH1Chb8=\ngithub.com/notaryproject/notation-go v1.3.2/go.mod h1:/1kuq5WuLF6Gaer5re0Z6HlkQRlKYO4EbWWT/L7J1Uw=\ngithub.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=\ngithub.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=\ngithub.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=\ngithub.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=\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.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=\ngithub.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=\ngithub.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=\ngithub.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=\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/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=\ngithub.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=\ngithub.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=\ngithub.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=\ngithub.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=\ngithub.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=\ngithub.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=\ngithub.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=\ngithub.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=\ngithub.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=\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-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/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.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=\ngithub.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=\ngithub.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=\ngithub.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=\ngithub.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=\ngithub.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=\ngithub.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=\ngithub.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho=\ngithub.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U=\ngithub.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc=\ngithub.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ=\ngithub.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=\ngithub.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=\ngithub.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=\ngithub.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\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/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/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=\ngithub.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\ngithub.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=\ngithub.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\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/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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\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/theckman/yacspin v0.13.12 h1:CdZ57+n0U6JMuh2xqjnjRq5Haj6v1ner2djtLQRzJr4=\ngithub.com/theckman/yacspin v0.13.12/go.mod h1:Rd2+oG2LmQi5f3zC3yeZAOl245z8QOvrH4OPOJNZxLg=\ngithub.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=\ngithub.com/vbatts/tar-split v0.12.2/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/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=\ngithub.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=\ngithub.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=\ngithub.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngitlab.com/gitlab-org/api/client-go v1.29.0 h1:3KnF6vENry/9v9eVrnLi2OfBV0m/WSrwh3RcxgH/hkA=\ngitlab.com/gitlab-org/api/client-go v1.29.0/go.mod h1:6i3EZtC6gKiTTmDwp+f6r/Yi9OY4AaYubl5B3yXEdHE=\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/bridges/prometheus v0.65.0 h1:I/7S/yWobR3QHFLqHsJ8QOndoiFsj1VgHpQiq43KlUI=\ngo.opentelemetry.io/contrib/bridges/prometheus v0.65.0/go.mod h1:jPF6gn3y1E+nozCAEQj3c6NZ8KY+tvAgSVfvoOJUFac=\ngo.opentelemetry.io/contrib/exporters/autoexport v0.65.0 h1:2gApdml7SznX9szEKFjKjM4qGcGSvAybYLBY319XG3g=\ngo.opentelemetry.io/contrib/exporters/autoexport v0.65.0/go.mod h1:0QqAGlbHXhmPYACG3n5hNzO5DnEqqtg4VcK5pr22RI0=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=\ngo.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=\ngo.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40=\ngo.opentelemetry.io/otel/exporters/prometheus v0.62.0 h1:krvC4JMfIOVdEuNPTtQ0ZjCiXrybhv+uOHMfHRmnvVo=\ngo.opentelemetry.io/otel/exporters/prometheus v0.62.0/go.mod h1:fgOE6FM/swEnsVQCqCnbOfRV4tOnWPg7bVeo4izBuhQ=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 h1:ivlbaajBWJqhcCPniDqDJmRwj4lc6sRT+dCAVKNmxlQ=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0/go.mod h1:u/G56dEKDDwXNCVLsbSrllB2o8pbtFLUC4HpR66r2dc=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk=\ngo.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ=\ngo.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8=\ngo.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4=\ngo.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes=\ngo.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=\ngo.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=\ngo.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=\ngo.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=\ngo.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI=\ngo.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4=\ngo.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4=\ngo.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y=\ngo.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=\ngo.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=\ngo.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=\ngo.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=\ngo.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=\ngo.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=\ngo.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=\ngolang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=\ngolang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=\ngolang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=\ngolang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=\ngolang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=\ngolang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=\ngolang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=\ngolang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=\ngolang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=\ngolang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=\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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=\ngolang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=\ngomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=\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.261.0 h1:3DoJ2GGibaCxNi1lhdScNMx9fTW87ujKHDgyHMMYdoA=\ngoogle.golang.org/api v0.261.0/go.mod h1:nVH0ZK5C4tO0RdsMscleeTLY7I8m/Nt9IXxcXD2tfts=\ngoogle.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=\ngoogle.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=\ngoogle.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=\ngoogle.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=\ngopkg.in/evanphx/json-patch.v4 v4.13.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/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=\ngopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=\ngotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=\nhelm.sh/helm/v4 v4.1.3 h1:Abfmb+oJUtxoaXDyB2Jhw1zRk3hT6aFfHta+AXb8Lno=\nhelm.sh/helm/v4 v4.1.3/go.mod h1:5dSo8rRgn3OTkDAc/k0Ipw5/Q+BlqKIKZwa0XwSiINI=\nk8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=\nk8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=\nk8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpApE29c0=\nk8s.io/apiextensions-apiserver v0.35.2/go.mod h1:OdyGvcO1FtMDWQ+rRh/Ei3b6X3g2+ZDHd0MSRGeS8rU=\nk8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=\nk8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/cli-runtime v0.35.2 h1:3DNctzpPNXavqyrm/FFiT60TLk4UjUxuUMYbKOE970E=\nk8s.io/cli-runtime v0.35.2/go.mod h1:G2Ieu0JidLm5m1z9b0OkFhnykvJ1w+vjbz1tR5OFKL0=\nk8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=\nk8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=\nk8s.io/component-base v0.35.2 h1:btgR+qNrpWuRSuvWSnQYsZy88yf5gVwemvz0yw79pGc=\nk8s.io/component-base v0.35.2/go.mod h1:B1iBJjooe6xIJYUucAxb26RwhAjzx0gHnqO9htWIX+0=\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-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=\nk8s.io/kubectl v0.35.2 h1:aSmqhSOfsoG9NR5oR8OD5eMKpLN9x8oncxfqLHbJJII=\nk8s.io/kubectl v0.35.2/go.mod h1:+OJC779UsDJGxNPbHxCwvb4e4w9Eh62v/DNYU2TlsyM=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=\nsigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs=\nsigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI=\nsigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI=\nsigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=\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/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "install/README.md",
    "content": "# flux CLI Installation\n\nBinaries for macOS and Linux AMD64 are available for download on the \n[release page](https://github.com/fluxcd/flux2/releases).\n\nTo install the latest release run:\n\n```bash\ncurl -s https://raw.githubusercontent.com/fluxcd/flux2/main/install/flux.sh | sudo bash\n```\n\n**Note**: You may want to export the `GITHUB_TOKEN` environment variable using a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)\nto avoid GitHub API rate-limiting errors if executing the install script repeatedly during a short time frame.\n\nThe install script does the following:\n* attempts to detect your OS\n* downloads and unpacks the release tar file in a temporary directory\n* copies the flux binary to `/usr/local/bin`\n* removes the temporary directory\n\nIf you want to use flux as a kubectl plugin, rename the binary to `kubectl-flux`:\n\n```sh\nmv /usr/local/bin/flux /usr/local/bin/kubectl-flux\n```\n\n## Build from source\n\nClone the repository:\n\n```bash\ngit clone https://github.com/fluxcd/flux2\ncd flux2\n```\n\nBuild the `flux` binary (requires go >= 1.15):\n\n```bash\nmake build\n```\n\nRun the binary:\n\n```bash\n./bin/flux -h\n```\n"
  },
  {
    "path": "install/flux.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\nDEFAULT_BIN_DIR=\"/usr/local/bin\"\nBIN_DIR=${1:-\"${DEFAULT_BIN_DIR}\"}\nGITHUB_REPO=\"fluxcd/flux2\"\n\n# Helper functions for logs\ninfo() {\n    echo '[INFO] ' \"$@\"\n}\n\nwarn() {\n    echo '[WARN] ' \"$@\" >&2\n}\n\nfatal() {\n    echo '[ERROR] ' \"$@\" >&2\n    exit 1\n}\n\n# Set os, fatal if operating system not supported\nsetup_verify_os() {\n    if [[ -z \"${OS}\" ]]; then\n        OS=$(uname)\n    fi\n    case ${OS} in\n        Darwin)\n            OS=darwin\n            ;;\n        Linux)\n            OS=linux\n            ;;\n        *)\n            fatal \"Unsupported operating system ${OS}\"\n    esac\n}\n\n# Set arch, fatal if architecture not supported\nsetup_verify_arch() {\n    if [[ -z \"${ARCH}\" ]]; then\n        ARCH=$(uname -m)\n    fi\n    case ${ARCH} in\n        arm|armv6l|armv7l)\n            ARCH=arm\n            ;;\n        arm64|aarch64|armv8l)\n            ARCH=arm64\n            ;;\n        amd64)\n            ARCH=amd64\n            ;;\n        x86_64)\n            ARCH=amd64\n            ;;\n        *)\n            fatal \"Unsupported architecture ${ARCH}\"\n    esac\n}\n\n# Verify existence of downloader executable\nverify_downloader() {\n    # Return failure if it doesn't exist or is no executable\n    [[ -x \"$(which \"$1\")\" ]] || return 1\n\n    # Set verified executable as our downloader program and return success\n    DOWNLOADER=$1\n    return 0\n}\n\n# Create tempory directory and cleanup when done\nsetup_tmp() {\n    TMP_DIR=$(mktemp -d -t flux-install.XXXXXXXXXX)\n    TMP_METADATA=\"${TMP_DIR}/flux.json\"\n    TMP_HASH=\"${TMP_DIR}/flux.hash\"\n    TMP_BIN=\"${TMP_DIR}/flux.tar.gz\"\n    cleanup() {\n        local code=$?\n        set +e\n        trap - EXIT\n        rm -rf \"${TMP_DIR}\"\n        exit ${code}\n    }\n    trap cleanup INT EXIT\n}\n\n# Find version from Github metadata\nget_release_version() {\n    if [[ -n \"${FLUX_VERSION}\" ]]; then\n      SUFFIX_URL=\"tags/v${FLUX_VERSION}\"\n    else\n      SUFFIX_URL=\"latest\"\n    fi\n\n    METADATA_URL=\"https://api.github.com/repos/${GITHUB_REPO}/releases/${SUFFIX_URL}\"\n\n    info \"Downloading metadata ${METADATA_URL}\"\n    download \"${TMP_METADATA}\" \"${METADATA_URL}\"\n\n    VERSION_FLUX=$(grep '\"tag_name\":' \"${TMP_METADATA}\" | sed -E 's/.*\"([^\"]+)\".*/\\1/' | cut -c 2-)\n    if [[ -n \"${VERSION_FLUX}\" ]]; then\n        info \"Using ${VERSION_FLUX} as release\"\n    else\n        fatal \"Unable to determine release version\"\n    fi\n}\n\n# Download from file from URL\ndownload() {\n    [[ $# -eq 2 ]] || fatal 'download needs exactly 2 arguments'\n\n    case $DOWNLOADER in\n        curl)\n            curl -u user:$GITHUB_TOKEN -o \"$1\" -sfL \"$2\"\n            ;;\n        wget)\n            wget --auth-no-challenge --user=user --password=$GITHUB_TOKEN -qO \"$1\" \"$2\"\n            ;;\n        *)\n            fatal \"Incorrect executable '${DOWNLOADER}'\"\n            ;;\n    esac\n\n    # Abort if download command failed\n    [[ $? -eq 0 ]] || fatal 'Download failed'\n}\n\n# Version comparison\n# Returns 0 on '=', 1 on '>', and 2 on '<'.\n# Ref: https://stackoverflow.com/a/4025065\nvercomp () {\n    if [[ $1 == $2 ]]\n    then\n        return 0\n    fi\n    local IFS=.\n    local i ver1=($1) ver2=($2)\n    # fill empty fields in ver1 with zeros\n    for ((i=${#ver1[@]}; i<${#ver2[@]}; i++))\n    do\n        ver1[i]=0\n    done\n    for ((i=0; i<${#ver1[@]}; i++))\n    do\n        if [[ -z ${ver2[i]} ]]\n        then\n            # fill empty fields in ver2 with zeros\n            ver2[i]=0\n        fi\n        if ((10#${ver1[i]} > 10#${ver2[i]}))\n        then\n            return 1\n        fi\n        if ((10#${ver1[i]} < 10#${ver2[i]}))\n        then\n            return 2\n        fi\n    done\n    return 0\n}\n\n# Download hash from Github URL\ndownload_hash() {\n    HASH_URL=\"https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_FLUX}/flux_${VERSION_FLUX}_checksums.txt\"\n    # NB: support the checksum filename format prior to v0.6.0\n    set +e\n    vercomp ${VERSION_FLUX} 0.6.0\n    if [[ $? -eq 2 ]]; then\n        HASH_URL=\"https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_FLUX}/flux2_${VERSION_FLUX}_checksums.txt\"\n    fi\n    set -e\n\n    info \"Downloading hash ${HASH_URL}\"\n    download \"${TMP_HASH}\" \"${HASH_URL}\"\n    HASH_EXPECTED=$(grep \" flux_${VERSION_FLUX}_${OS}_${ARCH}.tar.gz$\" \"${TMP_HASH}\")\n    HASH_EXPECTED=${HASH_EXPECTED%%[[:blank:]]*}\n}\n\n# Download binary from Github URL\ndownload_binary() {\n    BIN_URL=\"https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_FLUX}/flux_${VERSION_FLUX}_${OS}_${ARCH}.tar.gz\"\n    info \"Downloading binary ${BIN_URL}\"\n    download \"${TMP_BIN}\" \"${BIN_URL}\"\n}\n\ncompute_sha256sum() {\n  cmd=$(which sha256sum shasum | head -n 1)\n  case $(basename \"$cmd\") in\n    sha256sum)\n      sha256sum \"$1\" | cut -f 1 -d ' '\n      ;;\n    shasum)\n      shasum -a 256 \"$1\" | cut -f 1 -d ' '\n      ;;\n    *)\n      fatal \"Can not find sha256sum or shasum to compute checksum\"\n      ;;\n  esac\n}\n\n# Verify downloaded binary hash\nverify_binary() {\n    info \"Verifying binary download\"\n    HASH_BIN=$(compute_sha256sum \"${TMP_BIN}\")\n    HASH_BIN=${HASH_BIN%%[[:blank:]]*}\n    if [[ \"${HASH_EXPECTED}\" != \"${HASH_BIN}\" ]]; then\n        fatal \"Download sha256 does not match ${HASH_EXPECTED}, got ${HASH_BIN}\"\n    fi\n}\n\n# Setup permissions and move binary\nsetup_binary() {\n    chmod 755 \"${TMP_BIN}\"\n    info \"Installing flux to ${BIN_DIR}/flux\"\n    tar -xzof \"${TMP_BIN}\" -C \"${TMP_DIR}\"\n\n    local CMD_MOVE=\"mv -f \\\"${TMP_DIR}/flux\\\" \\\"${BIN_DIR}\\\"\"\n    if [[ -w \"${BIN_DIR}\" ]]; then\n        eval \"${CMD_MOVE}\"\n    else\n        eval \"sudo ${CMD_MOVE}\"\n    fi\n}\n\n# Run the install process\n{\n    setup_verify_os\n    setup_verify_arch\n    verify_downloader curl || verify_downloader wget || fatal 'Can not find curl or wget for downloading files'\n    setup_tmp\n    get_release_version\n    download_hash\n    download_binary\n    verify_binary\n    setup_binary\n}\n"
  },
  {
    "path": "internal/build/build.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage build\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/theckman/yacspin\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\tk8syaml \"k8s.io/apimachinery/pkg/util/yaml\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/kustomize/api/resmap\"\n\t\"sigs.k8s.io/kustomize/api/resource\"\n\t\"sigs.k8s.io/kustomize/kyaml/yaml\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/kustomize\"\n\trunclient \"github.com/fluxcd/pkg/runtime/client\"\n\tssautil \"github.com/fluxcd/pkg/ssa/utils\"\n\t\"sigs.k8s.io/kustomize/kyaml/filesys\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nconst (\n\tcontrollerName       = \"kustomize-controller\"\n\tcontrollerGroup      = \"kustomize.toolkit.fluxcd.io\"\n\tmask                 = \"**SOPS**\"\n\tdockercfgSecretType  = \"kubernetes.io/dockerconfigjson\"\n\ttypeField            = \"type\"\n\tdataField            = \"data\"\n\tstringDataField      = \"stringData\"\n\tspinnerDryRunMessage = \"running dry-run\"\n)\n\nvar defaultTimeout = 80 * time.Second\n\n// Builder builds yaml manifests\n// It retrieves the kustomization object from the k8s cluster\n// and overlays the manifests with the resources specified in the resourcesPath\ntype Builder struct {\n\tclient            client.WithWatch\n\trestMapper        meta.RESTMapper\n\tname              string\n\tnamespace         string\n\tresourcesPath     string\n\tkustomizationFile string\n\tignore            []string\n\t// mu is used to synchronize access to the kustomization file\n\tmu            sync.Mutex\n\taction        kustomize.Action\n\tkustomization *kustomizev1.Kustomization\n\ttimeout       time.Duration\n\tspinner       *yacspin.Spinner\n\tdryRun        bool\n\tstrictSubst   bool\n\trecursive     bool\n\tlocalSources  map[string]string\n\t// diff needs to handle kustomizations one by one\n\tsingleKustomization bool\n}\n\n// BuilderOptionFunc is a function that configures a Builder\ntype BuilderOptionFunc func(b *Builder) error\n\n// WithKustomizationFile sets the kustomization file\nfunc WithKustomizationFile(file string) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.kustomizationFile = file\n\t\treturn nil\n\t}\n}\n\n// WithTimeout sets the timeout for the builder\nfunc WithTimeout(timeout time.Duration) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.timeout = timeout\n\t\treturn nil\n\t}\n}\n\nfunc WithProgressBar() BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\t// Add a spinner\n\t\tcfg := yacspin.Config{\n\t\t\tFrequency:       100 * time.Millisecond,\n\t\t\tCharSet:         yacspin.CharSets[59],\n\t\t\tSuffix:          \"Kustomization diffing...\",\n\t\t\tSuffixAutoColon: true,\n\t\t\tMessage:         spinnerDryRunMessage,\n\t\t\tStopCharacter:   \"✓\",\n\t\t\tStopColors:      []string{\"fgGreen\"},\n\t\t}\n\t\tspinner, err := yacspin.New(cfg)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create spinner: %w\", err)\n\t\t}\n\t\tb.spinner = spinner\n\n\t\treturn nil\n\t}\n}\n\n// WithClientConfig sets the client configuration\nfunc WithClientConfig(rcg *genericclioptions.ConfigFlags, clientOpts *runclient.Options) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tkubeClient, err := utils.KubeClient(rcg, clientOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trestMapper, err := rcg.ToRESTMapper()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tb.client = kubeClient\n\t\tb.restMapper = restMapper\n\t\tb.namespace = *rcg.Namespace\n\t\treturn nil\n\t}\n}\n\n// WithNamespace sets the namespace\nfunc WithNamespace(namespace string) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.namespace = namespace\n\t\treturn nil\n\t}\n}\n\n// WithDryRun sets the dry-run flag\nfunc WithDryRun(dryRun bool) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.dryRun = dryRun\n\t\treturn nil\n\t}\n}\n\n// WithStrictSubstitute sets the strict substitute flag\nfunc WithStrictSubstitute(strictSubstitute bool) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.strictSubst = strictSubstitute\n\t\treturn nil\n\t}\n}\n\n// WithIgnore sets ignore field\nfunc WithIgnore(ignore []string) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.ignore = ignore\n\t\treturn nil\n\t}\n}\n\n// WithRecursive sets the recursive field\nfunc WithRecursive(recursive bool) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.recursive = recursive\n\t\treturn nil\n\t}\n}\n\n// WithLocalSources sets the local sources field\nfunc WithLocalSources(localSources map[string]string) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.localSources = localSources\n\t\treturn nil\n\t}\n}\n\n// WithSingleKustomization sets the single kustomization field to true\nfunc WithSingleKustomization() BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.singleKustomization = true\n\t\treturn nil\n\t}\n}\n\n// withClientConfigFrom copies client and restMapper fields\nfunc withClientConfigFrom(in *Builder) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.client = in.client\n\t\tb.restMapper = in.restMapper\n\t\treturn nil\n\t}\n}\n\n// withClientConfigFrom copies spinner field\nfunc withSpinnerFrom(in *Builder) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.spinner = in.spinner\n\t\treturn nil\n\t}\n}\n\n// withKustomization sets the kustomization field\nfunc withKustomization(k *kustomizev1.Kustomization) BuilderOptionFunc {\n\treturn func(b *Builder) error {\n\t\tb.kustomization = k\n\t\treturn nil\n\t}\n}\n\n// NewBuilder returns a new Builder\n// It takes a kustomization name and a path to the resources\n// It also takes a list of BuilderOptionFunc to configure the builder\n// One of the options is WithClientConfig, that must be provided for the builder to work\n// with the k8s cluster\n// One other option is WithKustomizationFile, that must be provided for the builder to work\n// with a local kustomization file. If the kustomization file is not provided, the builder\n// will try to retrieve the kustomization object from the k8s cluster.\n// WithDryRun sets the dry-run flag, and needs to be provided if the builder is used for\n// a dry-run. This flag works in conjunction with WithKustomizationFile, because the\n// kustomization object is not retrieved from the k8s cluster when the dry-run flag is set.\nfunc NewBuilder(name, resources string, opts ...BuilderOptionFunc) (*Builder, error) {\n\tb := &Builder{\n\t\tname:          name,\n\t\tresourcesPath: resources,\n\t}\n\n\tfor _, opt := range opts {\n\t\tif err := opt(b); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif b.timeout == 0 {\n\t\tb.timeout = defaultTimeout\n\t}\n\n\tif b.dryRun && b.kustomizationFile == \"\" && b.kustomization == nil {\n\t\treturn nil, fmt.Errorf(\"kustomization file is required for dry-run\")\n\t}\n\n\tif !b.dryRun && b.client == nil {\n\t\treturn nil, fmt.Errorf(\"client is required for live run\")\n\t}\n\n\treturn b, nil\n}\n\nfunc (b *Builder) resolveKustomization(liveKus *kustomizev1.Kustomization) (k *kustomizev1.Kustomization, err error) {\n\t// local kustomization file takes precedence over live kustomization\n\tif b.kustomizationFile != \"\" {\n\t\tk, err = b.unMarshallKustomization()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif !b.dryRun && liveKus != nil && liveKus.Status.Inventory != nil {\n\t\t\t// merge the live kustomization status with the local kustomization in order to get the\n\t\t\t// live resources status\n\t\t\tk.Status = *liveKus.Status.DeepCopy()\n\t\t}\n\t} else {\n\t\tk = liveKus\n\t}\n\treturn\n}\n\nfunc (b *Builder) getKustomization(ctx context.Context) (*kustomizev1.Kustomization, error) {\n\tliveKus := &kustomizev1.Kustomization{}\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: b.namespace,\n\t\tName:      b.name,\n\t}\n\terr := b.client.Get(ctx, namespacedName, liveKus)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn liveKus, nil\n}\n\n// Build builds the yaml manifests from the kustomization object\n// and overlays the manifests with the resources specified in the resourcesPath\n// It expects a kustomization.yaml file in the resourcesPath, and it will\n// generate a kustomization.yaml file if it doesn't exist\nfunc (b *Builder) Build() ([]*unstructured.Unstructured, error) {\n\tm, err := b.build()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources, err := m.AsYaml()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"kustomize build failed: %w\", err)\n\t}\n\n\tobjects, err := ssautil.ReadObjects(bytes.NewReader(resources))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"kustomize build failed: %w\", err)\n\t}\n\n\tif m := b.kustomization.Spec.CommonMetadata; m != nil {\n\t\tssautil.SetCommonMetadata(objects, m.Labels, m.Annotations)\n\t}\n\n\tif b.recursive && !b.singleKustomization {\n\t\tvar objectsToAdd []*unstructured.Unstructured\n\t\tfor _, obj := range objects {\n\t\t\tif isKustomization(obj) {\n\t\t\t\tk, err := toKustomization(obj)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif !kustomizationsEqual(k, b.kustomization) {\n\t\t\t\t\tsubObjects, err := b.kustomizationBuild(k)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tobjectsToAdd = append(objectsToAdd, subObjects...)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tobjects = append(objects, objectsToAdd...)\n\t}\n\n\treturn objects, nil\n}\n\nfunc (b *Builder) build() (m resmap.ResMap, err error) {\n\tctx, cancel := context.WithTimeout(context.Background(), b.timeout)\n\tdefer cancel()\n\n\t// Get the kustomization object\n\tliveKus := &kustomizev1.Kustomization{}\n\tif b.dryRun {\n\t\tliveKus = b.kustomization\n\t} else {\n\t\tliveKus, err = b.getKustomization(ctx)\n\t\tif err != nil {\n\t\t\tif !apierrors.IsNotFound(err) || b.kustomization == nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to get kustomization object: %w\", err)\n\t\t\t}\n\t\t\t// use provided Kustomization\n\t\t\tliveKus = b.kustomization\n\t\t}\n\t}\n\n\tk, err := b.resolveKustomization(liveKus)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed to get kustomization object: %w\", err)\n\t\treturn\n\t}\n\n\t// store the kustomization object\n\tb.kustomization = k\n\n\t// generate kustomization.yaml if needed\n\taction, er := b.generate(*k, b.resourcesPath)\n\tif er != nil {\n\t\terrf := kustomize.CleanDirectory(b.resourcesPath, action)\n\t\terr = fmt.Errorf(\"failed to generate kustomization.yaml: %w\", fmt.Errorf(\"%v %v\", er, errf))\n\t\treturn\n\t}\n\n\tb.action = action\n\n\tdefer func() {\n\t\terrf := b.Cancel()\n\t\tif err == nil {\n\t\t\terr = errf\n\t\t}\n\t}()\n\n\t// build the kustomization\n\tm, err = b.do(ctx, *k, b.resourcesPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, res := range m.Resources() {\n\t\t// set owner labels\n\t\terr = b.setOwnerLabels(res)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t// make sure secrets are masked\n\t\terr = maskSopsData(res)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n\n}\n\nfunc (b *Builder) kustomizationBuild(k *kustomizev1.Kustomization) ([]*unstructured.Unstructured, error) {\n\tresourcesPath, err := b.kustomizationPath(k)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsubBuilder, err := NewBuilder(k.Name, resourcesPath,\n\t\t// use same client\n\t\twithClientConfigFrom(b),\n\t\t// kustomization will be used if there is no live kustomization\n\t\twithKustomization(k),\n\t\tWithTimeout(b.timeout),\n\t\tWithNamespace(k.Namespace),\n\t\tWithIgnore(b.ignore),\n\t\tWithStrictSubstitute(b.strictSubst),\n\t\tWithRecursive(b.recursive),\n\t\tWithLocalSources(b.localSources),\n\t\tWithDryRun(b.dryRun),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn subBuilder.Build()\n}\n\nfunc (b *Builder) kustomizationPath(k *kustomizev1.Kustomization) (string, error) {\n\tsourceRef := k.Spec.SourceRef.DeepCopy()\n\tif sourceRef.Namespace == \"\" {\n\t\tsourceRef.Namespace = k.Namespace\n\t}\n\n\tsourceKey := sourceRef.String()\n\tlocalPath, ok := b.localSources[sourceKey]\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"cannot get local path for %s of kustomization %s\", sourceKey, k.Name)\n\t}\n\n\treturn filepath.Join(localPath, k.Spec.Path), nil\n}\n\nfunc (b *Builder) unMarshallKustomization() (*kustomizev1.Kustomization, error) {\n\tdata, err := os.ReadFile(b.kustomizationFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read kustomization file %s: %w\", b.kustomizationFile, err)\n\t}\n\tk := &kustomizev1.Kustomization{}\n\tdecoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(data), len(data))\n\t// check for kustomization in yaml with the same name and namespace\n\tfor {\n\t\t// ensure the target struct is emptied before decoding\n\t\tk = &kustomizev1.Kustomization{}\n\t\terr = decoder.Decode(k)\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\treturn nil, fmt.Errorf(\"failed find kustomization with name '%s' and namespace '%s' in file '%s'\",\n\t\t\t\t\tb.name, b.namespace, b.kustomizationFile)\n\t\t\t} else {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to unmarshall kustomization file %s: %w\", b.kustomizationFile, err)\n\t\t\t}\n\t\t}\n\n\t\tif strings.HasPrefix(k.APIVersion, kustomizev1.GroupVersion.Group+\"/\") &&\n\t\t\tk.Kind == kustomizev1.KustomizationKind &&\n\t\t\tk.Name == b.name &&\n\t\t\t(k.Namespace == b.namespace || k.Namespace == \"\") {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn k, nil\n}\n\nfunc (b *Builder) generate(kustomization kustomizev1.Kustomization, dirPath string) (kustomize.Action, error) {\n\tdata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&kustomization)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// a scanner will be used down the line to parse the list\n\t// so we have to make sure to include newlines\n\tignoreList := strings.Join(b.ignore, \"\\n\")\n\tgen := kustomize.NewGeneratorWithIgnore(\"\", ignoreList, unstructured.Unstructured{Object: data})\n\n\t// acquire the lock\n\tb.mu.Lock()\n\tdefer b.mu.Unlock()\n\n\treturn gen.WriteFile(dirPath, kustomize.WithSaveOriginalKustomization())\n}\n\nfunc (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomization, dirPath string) (resmap.ResMap, error) {\n\tfs := filesys.MakeFsOnDisk()\n\n\t// acquire the lock\n\tb.mu.Lock()\n\tdefer b.mu.Unlock()\n\n\tm, err := kustomize.Build(fs, dirPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"kustomize build failed: %w\", err)\n\t}\n\n\tif kustomization.Spec.PostBuild == nil {\n\t\treturn m, nil\n\t}\n\n\tdata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&kustomization)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, res := range m.Resources() {\n\t\t// run variable substitutions\n\t\toutRes, err := kustomize.SubstituteVariables(ctx,\n\t\t\tb.client,\n\t\t\tunstructured.Unstructured{Object: data},\n\t\t\tres,\n\t\t\tkustomize.SubstituteWithDryRun(b.dryRun),\n\t\t\tkustomize.SubstituteWithStrict(b.strictSubst),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"var substitution failed for '%s': %w\", res.GetName(), err)\n\t\t}\n\n\t\tif outRes != nil {\n\t\t\t_, err = m.Replace(res)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc isKustomization(object *unstructured.Unstructured) bool {\n\treturn strings.HasPrefix(object.GetAPIVersion(), kustomizev1.GroupVersion.Group+\"/\") &&\n\t\tobject.GetKind() == kustomizev1.KustomizationKind\n}\n\nfunc toKustomization(object *unstructured.Unstructured) (*kustomizev1.Kustomization, error) {\n\tobj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(object)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert to unstructured: %w\", err)\n\t}\n\tk := &kustomizev1.Kustomization{}\n\terr = runtime.DefaultUnstructuredConverter.FromUnstructured(obj, k)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert to kustomization: %w\", err)\n\t}\n\treturn k, nil\n}\n\nfunc kustomizationsEqual(k1 *kustomizev1.Kustomization, k2 *kustomizev1.Kustomization) bool {\n\treturn k1.Name == k2.Name && k1.Namespace == k2.Namespace\n}\n\nfunc (b *Builder) setOwnerLabels(res *resource.Resource) error {\n\tlabels := res.GetLabels()\n\n\tlabels[controllerGroup+\"/name\"] = b.kustomization.GetName()\n\tlabels[controllerGroup+\"/namespace\"] = b.kustomization.GetNamespace()\n\n\terr := res.SetLabels(labels)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc maskSopsData(res *resource.Resource) error {\n\t// sopsMess is the base64 encoded mask\n\tsopsMess := base64.StdEncoding.EncodeToString([]byte(mask))\n\n\tif res.GetKind() == \"Secret\" {\n\t\t// get both data and stringdata maps as a secret can have both\n\t\tdataMap := res.GetDataMap()\n\t\tstringDataMap := getStringDataMap(res)\n\t\tasYaml, err := res.AsYAML()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to mask secret %s sops data: %w\", res.GetName(), err)\n\t\t}\n\n\t\t// delete any sops data as we don't want to expose it\n\t\t// assume that both data and stringdata are encrypted\n\t\tif bytes.Contains(asYaml, []byte(\"sops:\")) && bytes.Contains(asYaml, []byte(\"mac: ENC[\")) {\n\t\t\t// delete the sops object\n\t\t\tres.PipeE(yaml.FieldClearer{Name: \"sops\"})\n\n\t\t\tsecretType, err := res.GetFieldValue(typeField)\n\t\t\t// If the intended type is Opaque, then it can be omitted from the manifest, since it's the default\n\t\t\t// Ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets\n\t\t\tif errors.As(err, &yaml.NoFieldError{}) {\n\t\t\t\tsecretType = \"Opaque\"\n\t\t\t} else if err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to mask secret %s sops data: %w\", res.GetName(), err)\n\t\t\t}\n\n\t\t\tif v, ok := secretType.(string); ok && v == dockercfgSecretType {\n\t\t\t\t// if the secret is a json docker config secret, we need to mask the data with a json object\n\t\t\t\terr := maskDockerconfigjsonSopsData(dataMap, true)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to mask secret %s sops data: %w\", res.GetName(), err)\n\t\t\t\t}\n\n\t\t\t\terr = maskDockerconfigjsonSopsData(stringDataMap, false)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to mask secret %s sops data: %w\", res.GetName(), err)\n\t\t\t\t}\n\n\t\t\t} else {\n\t\t\t\tfor k := range dataMap {\n\t\t\t\t\tdataMap[k] = sopsMess\n\t\t\t\t}\n\n\t\t\t\tfor k := range stringDataMap {\n\t\t\t\t\tstringDataMap[k] = mask\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\terr := maskBase64EncryptedSopsData(dataMap, sopsMess)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to mask secret %s sops data: %w\", res.GetName(), err)\n\t\t\t}\n\n\t\t\terr = maskSopsDataInStringDataSecret(stringDataMap, sopsMess)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to mask secret %s sops data: %w\", res.GetName(), err)\n\t\t\t}\n\t\t}\n\n\t\t// set the data and stringdata maps\n\t\tres.SetDataMap(dataMap)\n\n\t\tif len(stringDataMap) > 0 {\n\t\t\terr = res.SetMapField(yaml.NewMapRNode(&stringDataMap), stringDataField)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to mask secret %s sops data: %w\", res.GetName(), err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc getStringDataMap(rn *resource.Resource) map[string]string {\n\tn, err := rn.Pipe(yaml.Lookup(stringDataField))\n\tif err != nil {\n\t\treturn nil\n\t}\n\tresult := map[string]string{}\n\t_ = n.VisitFields(func(node *yaml.MapNode) error {\n\t\tresult[yaml.GetValue(node.Key)] = yaml.GetValue(node.Value)\n\t\treturn nil\n\t})\n\treturn result\n}\n\nfunc maskDockerconfigjsonSopsData(dataMap map[string]string, encode bool) error {\n\tsopsMess := struct {\n\t\tMask string `json:\"mask\"`\n\t}{\n\t\tMask: mask,\n\t}\n\n\tmaskJson, err := json.Marshal(sopsMess)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif encode {\n\t\tfor k := range dataMap {\n\t\t\tdataMap[k] = base64.StdEncoding.EncodeToString(maskJson)\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor k := range dataMap {\n\t\tdataMap[k] = string(maskJson)\n\t}\n\n\treturn nil\n}\n\nfunc maskBase64EncryptedSopsData(dataMap map[string]string, mask string) error {\n\tfor k, v := range dataMap {\n\t\tdata, err := base64.StdEncoding.DecodeString(v)\n\t\tif corruptErr := base64.CorruptInputError(0); errors.As(err, &corruptErr) {\n\t\t\treturn corruptErr\n\t\t}\n\n\t\tif bytes.Contains(data, []byte(\"sops\")) && bytes.Contains(data, []byte(\"ENC[\")) {\n\t\t\tdataMap[k] = mask\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc maskSopsDataInStringDataSecret(stringDataMap map[string]string, mask string) error {\n\tfor k, v := range stringDataMap {\n\t\tif bytes.Contains([]byte(v), []byte(\"sops\")) && bytes.Contains([]byte(v), []byte(\"ENC[\")) {\n\t\t\tstringDataMap[k] = mask\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Cancel cancels the build\n// It restores a clean repository\nfunc (b *Builder) Cancel() error {\n\t// acquire the lock\n\tb.mu.Lock()\n\tdefer b.mu.Unlock()\n\n\terr := kustomize.CleanDirectory(b.resourcesPath, b.action)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (b *Builder) StartSpinner() error {\n\tif b.spinner == nil {\n\t\treturn nil\n\t}\n\n\terr := b.spinner.Start()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to start spinner: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Builder) StopSpinner() error {\n\tif b.spinner == nil {\n\t\treturn nil\n\t}\n\n\tstatus := b.spinner.Status()\n\tif status == yacspin.SpinnerRunning || status == yacspin.SpinnerPaused {\n\t\terr := b.spinner.Stop()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to stop spinner: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/build/build_test.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage build\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\t\"github.com/google/go-cmp/cmp\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"sigs.k8s.io/kustomize/api/resource\"\n\t\"sigs.k8s.io/kustomize/kyaml/yaml\"\n)\n\nfunc TestTrimSopsData(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tyamlStr  string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"secret with sops token\",\n\t\t\tyamlStr: `apiVersion: v1\nkind: Secret\nmetadata:\n  name: my-secret\ntype: Opaque\ndata:\n  token: |\n    ewoJImRhdGEiOiAiRU5DW0FFUzI1Nl9HQ00sZGF0YTpvQmU1UGxQbWZRQ1VVYzRzcUtJbW\n    p3PT0saXY6TUxMRVcxNVFDOWtSZFZWYWdKbnpMQ1NrMHhaR1dJcEFlVGZIenl4VDEwZz0s\n    dGFnOkszR2tCQ0dTK3V0NFRwazZuZGIwQ0E9PSx0eXBlOnN0cl0iLAoJInNvcHMiOiB7Cg\n    kJImttcyI6IG51bGwsCgkJImdjcF9rbXMiOiBudWxsLAoJCSJhenVyZV9rdiI6IG51bGws\n    CgkJImhjX3ZhdWx0IjogbnVsbCwKCQkiYWdlIjogWwoJCQl7CgkJCQkicmVjaXBpZW50Ij\n    ogImFnZTEwbGEyZ2Uwd3R2eDNxcjdkYXRxZjdyczR5bmd4c3pkYWw5MjdmczlydWthbXI4\n    dTJwc2hzdnR6N2NlIiwKCQkJCSJlbmMiOiAiLS0tLS1CRUdJTiBBR0UgRU5DUllQVEVEIE\n    ZJTEUtLS0tLVxuWVdkbExXVnVZM0o1Y0hScGIyNHViM0puTDNZeENpMCtJRmd5TlRVeE9T\n    QTFMMlJwWkhScksxRlNWbVlyZDFWYVxuWTBoeFdGUXpTREJzVDFrM1dqTnRZbVUxUW1saW\n    FESnljWGxOQ25GMVlqZE5PVGhWYlZOdk1HOXJOUzlaVVhad1xuTW5WMGJuUlVNR050ZWpG\n    UGJ6TTRVMlV6V2tzemVWa0tMUzB0SUdKNlVHaHhNVVYzWW1WSlRIbEpTVUpwUlZSWlxuVm\n    pkMFJWUmFkVTh3ZWt4WFRISXJZVXBsWWtOMmFFRUswSS9NQ0V0WFJrK2IvTjJHMUpGM3ZI\n    UVQyNGRTaFdZRFxudytKSVVTQTNhTGYyc3YwenIyTWRVRWRWV0JKb004blQ0RDR4VmJCT1\n    JEKzY2OVcrOW5EZVN3PT1cbi0tLS0tRU5EIEFHRSBFTkNSWVBURUQgRklMRS0tLS0tXG4i\n    CgkJCX0KCQldLAoJCSJsYXN0bW9kaWZpZWQiOiAiMjAyMS0xMS0yNlQxNjozNDo1MVoiLA\n    oJCSJtYWMiOiAiRU5DW0FFUzI1Nl9HQ00sZGF0YTpDT0d6ZjVZQ0hOTlA2ejRKYUVLcmpO\n    M004ZjUrUTF1S1VLVE1Id2ozODgvSUNtTHlpMnNTclRtajdQUCtYN005alRWd2E4d1ZnWV\n    RwTkxpVkp4K0xjeHF2SVhNMFR5bysvQ3UxenJmYW85OGFpQUNQOCtUU0VEaUZRTnRFdXMy\n    M0grZC9YMWhxTXdSSERJM2tRKzZzY2dFR25xWTU3cjNSRFNBM0U4RWhIcjQ9LGl2Okx4aX\n    RWSVltOHNyWlZxRnVlSmg5bG9DbEE0NFkyWjNYQVZZbXhlc01tT2c9LHRhZzpZOHFGRDhV\n    R2xEZndOU3Y3eGxjbjZBPT0sdHlwZTpzdHJdIiwKCQkicGdwIjogbnVsbCwKCQkidW5lbm\n    NyeXB0ZWRfc3VmZml4IjogIl91bmVuY3J5cHRlZCIsCgkJInZlcnNpb24iOiAiMy43LjEi\n    Cgl9Cn0=\n`,\n\t\t\texpected: `apiVersion: v1\ndata:\n  token: KipTT1BTKio=\nkind: Secret\nmetadata:\n  name: my-secret\ntype: Opaque\n`,\n\t\t},\n\t\t{\n\t\t\tname: \"secret with basic auth\",\n\t\t\tyamlStr: `apiVersion: v1\ndata:\n  password: cGFzc3dvcmQK\n  username: YWRtaW4K\nkind: Secret\nmetadata:\n  name: secret-basic-auth\ntype: kubernetes.io/basic-auth\n`,\n\t\t\texpected: `apiVersion: v1\ndata:\n  password: cGFzc3dvcmQK\n  username: YWRtaW4K\nkind: Secret\nmetadata:\n  name: secret-basic-auth\ntype: kubernetes.io/basic-auth\n`,\n\t\t},\n\t\t{\n\t\t\tname: \"secret sops secret\",\n\t\t\tyamlStr: `apiVersion: v1\ndata:\n  .dockerconfigjson: ENC[AES256_GCM,data:KHCFH3hNnc+PMfWLFEPjebf3W4z4WXbGFAANRZyZC+07z7wlrTALJM6rn8YslW4tMAWCoAYxblC5WRCszTy0h9rw0U/RGOv5H0qCgnNg/FILFUqhwo9pNfrUH+MEP4M9qxxbLKZwObpHUE7DUsKx1JYAxsI=,iv:q48lqUbUQD+0cbYcjNMZMJLRdGHi78ZmDhNAT2th9tg=,tag:QRI2SZZXQrAcdql3R5AH2g==,type:str]\nkind: Secret\nmetadata:\n  name: secret\ntype: kubernetes.io/dockerconfigjson\nsops:\n  kms: []\n  gcp_kms: []\n  azure_kv: []\n  hc_vault: []\n  age:\n    - recipient: age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce\n      enc: |\n        -----BEGIN AGE ENCRYPTED FILE-----\n        YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3eU1CTEJhVXZ4eEVYYkVV\n        OU90TEcrR2pYckttN0pBanJoSUZWSW1RQXlRCkUydFJ3V1NZUTBuVFF0aC9GUEcw\n        bUdhNjJWTkoyL1FUVi9Dc1dxUDBkM0UKLS0tIE1sQXkwcWdGaEFuY0RHQTVXM0J6\n        dWpJcThEbW15V3dXYXpPZklBdW1Hd1kKoIAdmGNPrEctV8h1w8KuvQ5S+BGmgqN9\n        MgpNmUhJjWhgcQpb5BRYpQesBOgU5TBGK7j58A6DMDKlSiYZsdQchQ==\n        -----END AGE ENCRYPTED FILE-----\n  lastmodified: \"2022-02-03T16:03:17Z\"\n  mac: ENC[AES256_GCM,data:AHdYSawajwgAFwlmDN1IPNmT9vWaYKzyVIra2d6sPcjTbZ8/p+VRSRpVm4XZFFsaNnW5AUJaouwXnKYDTmJDXKlr/rQcu9kXqsssQgdzcXaA6l5uJlgsnml8ba7J3OK+iEKMax23mwQEx2EUskCd9ENOwFDkunP02sxqDNOz20k=,iv:8F5OamHt3fAVorf6p+SoIrWoqkcATSGWVoM0EK87S4M=,tag:E1mxXnc7wWkEX5BxhpLtng==,type:str]\n  pgp: []\n  encrypted_regex: ^(data|stringData)$\n  version: 3.7.1\n`,\n\t\t\texpected: `apiVersion: v1\ndata:\n  .dockerconfigjson: eyJtYXNrIjoiKipTT1BTKioifQ==\nkind: Secret\nmetadata:\n  name: secret\ntype: kubernetes.io/dockerconfigjson\n`,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tr, err := yaml.Parse(tc.yamlStr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unable to parse yaml: %v\", err)\n\t\t\t}\n\n\t\t\tresource := &resource.Resource{RNode: *r}\n\t\t\terr = maskSopsData(resource)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unable to trim sops data: %v\", err)\n\t\t\t}\n\n\t\t\tsYaml, err := resource.AsYAML()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unable to convert sanitized resources to yaml: %v\", err)\n\t\t\t}\n\t\t\tif diff := cmp.Diff(string(sYaml), tc.expected); diff != \"\" {\n\t\t\t\tt.Errorf(\"unexpected sanitized resources: (-got +want)%v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_unMarshallKustomization(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tlocalKsFile string\n\t\twantErr     bool\n\t\terrString   string\n\t}{\n\t\t{\n\t\t\tname:        \"valid kustomization\",\n\t\t\tlocalKsFile: \"testdata/local-kustomization/valid.yaml\",\n\t\t},\n\t\t{\n\t\t\tname:        \"Multi-doc yaml containing kustomization and other resources\",\n\t\t\tlocalKsFile: \"testdata/local-kustomization/multi-doc-valid.yaml\",\n\t\t},\n\t\t{\n\t\t\tname:        \"no namespace\",\n\t\t\tlocalKsFile: \"testdata/local-kustomization/no-ns.yaml\",\n\t\t},\n\t\t{\n\t\t\tname:        \"kustomization with a different name\",\n\t\t\tlocalKsFile: \"testdata/local-kustomization/different-name.yaml\",\n\t\t\twantErr:     true,\n\t\t\terrString:   \"failed find kustomization with name\",\n\t\t},\n\t\t{\n\t\t\tname:        \"yaml containing other resource with same name as kustomization\",\n\t\t\tlocalKsFile: \"testdata/local-kustomization/invalid-resource.yaml\",\n\t\t\twantErr:     true,\n\t\t\terrString:   \"failed find kustomization with name\",\n\t\t},\n\t}\n\n\tb := &Builder{\n\t\tname:      \"podinfo\",\n\t\tnamespace: \"flux-system\",\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tb.kustomizationFile = tt.localKsFile\n\t\t\tks, err := b.unMarshallKustomization()\n\t\t\tif !tt.wantErr {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected err '%s'\", err)\n\t\t\t\t}\n\n\t\t\t\tif ks.Name != b.name && ks.Namespace != b.namespace {\n\t\t\t\t\tt.Errorf(\"expected kustomization '%s/%s' to match '%s/%s'\",\n\t\t\t\t\t\tks.Name, ks.Namespace, b.name, b.namespace)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatal(\"expected error but got nil\")\n\t\t\t\t}\n\n\t\t\t\tif !strings.Contains(err.Error(), tt.errString) {\n\t\t\t\t\tt.Errorf(\"expected error '%s' to contain string '%s'\", err.Error(), tt.errString)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\tt.Run(\"correct parsing of multiple documents\", func(t *testing.T) {\n\t\tb.kustomizationFile = \"testdata/local-kustomization/multi-doc-reset.yaml\"\n\t\tks, err := b.unMarshallKustomization()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected err '%s'\", err)\n\t\t}\n\t\tif len(ks.Spec.Components) > 0 {\n\t\t\tt.Errorf(\"previous Kustomization in file leaked into subsequent Kustomizations\")\n\t\t}\n\t})\n}\n\nfunc Test_ResolveKustomization(t *testing.T) {\n\ttests := []struct {\n\t\tname              string\n\t\tlocalKsFile       string\n\t\tliveKustomization *kustomizev1.Kustomization\n\t\tdryrun            bool\n\t}{\n\t\t{\n\t\t\tname:        \"valid kustomization\",\n\t\t\tlocalKsFile: \"testdata/local-kustomization/valid.yaml\",\n\t\t},\n\t\t{\n\t\t\tname:        \"local and live kustomization\",\n\t\t\tlocalKsFile: \"testdata/local-kustomization/valid.yaml\",\n\t\t\tliveKustomization: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"podinfo\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t\tSpec: kustomizev1.KustomizationSpec{\n\t\t\t\t\tInterval: metav1.Duration{Duration: time.Minute * 5},\n\t\t\t\t\tPath:     \"./testdata/local-kustomization/valid.yaml\",\n\t\t\t\t},\n\t\t\t\tStatus: kustomizev1.KustomizationStatus{\n\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tType:   meta.ReadyCondition,\n\t\t\t\t\t\t\tStatus: metav1.ConditionTrue,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInventory: &kustomizev1.ResourceInventory{\n\t\t\t\t\t\tEntries: []kustomizev1.ResourceRef{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tID:      \"flux-system_podinfo_v1_service_podinfo\",\n\t\t\t\t\t\t\t\tVersion: \"v1\",\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:        \"local and live kustomization with dryrun\",\n\t\t\tlocalKsFile: \"testdata/local-kustomization/valid.yaml\",\n\t\t\tliveKustomization: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"podinfo\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t\tSpec: kustomizev1.KustomizationSpec{\n\t\t\t\t\tInterval: metav1.Duration{Duration: time.Minute * 5},\n\t\t\t\t\tPath:     \"./testdata/local-kustomization/valid.yaml\",\n\t\t\t\t},\n\t\t\t\tStatus: kustomizev1.KustomizationStatus{\n\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tType:   meta.ReadyCondition,\n\t\t\t\t\t\t\tStatus: metav1.ConditionTrue,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInventory: &kustomizev1.ResourceInventory{\n\t\t\t\t\t\tEntries: []kustomizev1.ResourceRef{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tID:      \"flux-system_podinfo_v1_service_podinfo\",\n\t\t\t\t\t\t\t\tVersion: \"v1\",\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\tdryrun: true,\n\t\t},\n\t\t{\n\t\t\tname: \"live kustomization\",\n\t\t\tliveKustomization: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"podinfo\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t\tSpec: kustomizev1.KustomizationSpec{\n\t\t\t\t\tInterval: metav1.Duration{Duration: time.Minute * 5},\n\t\t\t\t\tPath:     \"./testdata/local-kustomization/valid.yaml\",\n\t\t\t\t},\n\t\t\t\tStatus: kustomizev1.KustomizationStatus{\n\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tType:   meta.ReadyCondition,\n\t\t\t\t\t\t\tStatus: metav1.ConditionTrue,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tInventory: &kustomizev1.ResourceInventory{\n\t\t\t\t\t\tEntries: []kustomizev1.ResourceRef{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tID:      \"flux-system_podinfo_v1_service_podinfo\",\n\t\t\t\t\t\t\t\tVersion: \"v1\",\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\tb := &Builder{\n\t\tname:      \"podinfo\",\n\t\tnamespace: \"flux-system\",\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tb.kustomizationFile = tt.localKsFile\n\t\t\tb.dryRun = tt.dryrun\n\t\t\tks, err := b.resolveKustomization(tt.liveKustomization)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected err '%s'\", err)\n\t\t\t}\n\t\t\tif !tt.dryrun {\n\t\t\t\tif b.kustomizationFile == \"\" {\n\t\t\t\t\tif cmp.Diff(ks, tt.liveKustomization) != \"\" {\n\t\t\t\t\t\tt.Errorf(\"expected kustomization to match live kustomization\")\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif tt.liveKustomization != nil && cmp.Diff(ks.Status, tt.liveKustomization.Status) != \"\" {\n\t\t\t\t\t\tt.Errorf(\"expected kustomization status to match live kustomization status\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ks.Status.Inventory != nil {\n\t\t\t\t\tfmt.Println(ks.Status.Inventory)\n\t\t\t\t\tt.Errorf(\"expected kustomization status to be nil\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_isKustomization(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\texpected bool\n\t\tobject   *unstructured.Unstructured\n\t}{\n\t\t{\n\t\t\tname: \"flux kustomization\",\n\t\t\tobject: &unstructured.Unstructured{\n\t\t\t\tObject: map[string]interface{}{\n\t\t\t\t\t\"apiVersion\": \"kustomize.toolkit.fluxcd.io/v1\",\n\t\t\t\t\t\"kind\":       \"Kustomization\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname: \"other kustomization\",\n\t\t\tobject: &unstructured.Unstructured{\n\t\t\t\tObject: map[string]interface{}{\n\t\t\t\t\t\"apiVersion\": \"kustomize.config.k8s.io/v1beta1\",\n\t\t\t\t\t\"kind\":       \"Kustomization\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tname: \"wrong kind\",\n\t\t\tobject: &unstructured.Unstructured{\n\t\t\t\tObject: map[string]interface{}{\n\t\t\t\t\t\"apiVersion\": \"kustomize.toolkit.fluxcd.io/v1\",\n\t\t\t\t\t\"kind\":       \"ConfigMap\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tname: \"wrong object\",\n\t\t\tobject: &unstructured.Unstructured{\n\t\t\t\tObject: map[string]interface{}{\n\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\"kind\":       \"ConfigMap\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tactual := isKustomization(tt.object)\n\t\t\tif actual != tt.expected {\n\t\t\t\tt.Fatalf(\"got '%v', want '%v'\", actual, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_kustomizationsEqual(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tkustomization1 *kustomizev1.Kustomization\n\t\tkustomization2 *kustomizev1.Kustomization\n\t\texpected       bool\n\t}{\n\t\t{\n\t\t\tname: \"equal\",\n\t\t\tkustomization1: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"podinfo\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tkustomization2: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"podinfo\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname: \"wrong name\",\n\t\t\tkustomization1: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"podinfo\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tkustomization2: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"my-app\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tname: \"wrong namespace\",\n\t\t\tkustomization1: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"podinfo\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tkustomization2: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"podinfo\",\n\t\t\t\t\tNamespace: \"my-ns\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tname: \"wrong name and namespace\",\n\t\t\tkustomization1: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"podinfo\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tkustomization2: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"my-app\",\n\t\t\t\t\tNamespace: \"my-ns\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tactual := kustomizationsEqual(tt.kustomization1, tt.kustomization2)\n\t\t\tif actual != tt.expected {\n\t\t\t\tt.Fatalf(\"got '%v', want '%v'\", actual, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_kustomizationPath(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tkustomization *kustomizev1.Kustomization\n\t\texpected      string\n\t\twantErr       bool\n\t\terrString     string\n\t}{\n\t\t{\n\t\t\tname: \"full repo\",\n\t\t\tkustomization: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"my-app\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t\tSpec: kustomizev1.KustomizationSpec{\n\t\t\t\t\tPath: \"my-path\",\n\t\t\t\t\tSourceRef: kustomizev1.CrossNamespaceSourceReference{\n\t\t\t\t\t\tKind:      \"GitRepository\",\n\t\t\t\t\t\tName:      \"my-repo\",\n\t\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"path/to/local/git/my-path\",\n\t\t},\n\t\t{\n\t\t\tname: \"repo without namespace\",\n\t\t\tkustomization: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"my-app\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t\tSpec: kustomizev1.KustomizationSpec{\n\t\t\t\t\tPath: \"my-path\",\n\t\t\t\t\tSourceRef: kustomizev1.CrossNamespaceSourceReference{\n\t\t\t\t\t\tKind:      \"GitRepository\",\n\t\t\t\t\t\tName:      \"my-repo\",\n\t\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"path/to/local/git/my-path\",\n\t\t},\n\t\t{\n\t\t\tname: \"repo not found\",\n\t\t\tkustomization: &kustomizev1.Kustomization{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"my-app\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t\tSpec: kustomizev1.KustomizationSpec{\n\t\t\t\t\tPath: \"my-path\",\n\t\t\t\t\tSourceRef: kustomizev1.CrossNamespaceSourceReference{\n\t\t\t\t\t\tKind:      \"GitRepository\",\n\t\t\t\t\t\tName:      \"my-repo\",\n\t\t\t\t\t\tNamespace: \"my-ns\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:   true,\n\t\t\terrString: \"cannot get local path\",\n\t\t},\n\t}\n\n\tb := &Builder{\n\t\tname:      \"podinfo\",\n\t\tnamespace: \"flux-system\",\n\t\tlocalSources: map[string]string{\n\t\t\t\"GitRepository/flux-system/my-repo\": \"./path/to/local/git\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tactual, err := b.kustomizationPath(tt.kustomization)\n\t\t\tif !tt.wantErr {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected err '%s'\", err)\n\t\t\t\t}\n\n\t\t\t\tif actual != tt.expected {\n\t\t\t\t\tt.Errorf(\"got '%v', want '%v'\", actual, tt.expected)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatal(\"expected error but got nil\")\n\t\t\t\t}\n\n\t\t\t\tif !strings.Contains(err.Error(), tt.errString) {\n\t\t\t\t\tt.Errorf(\"expected error '%s' to contain string '%s'\", err.Error(), tt.errString)\n\t\t\t\t}\n\t\t\t}\n\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/build/diff.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage build\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/gonvenience/bunt\"\n\t\"github.com/gonvenience/ytbx\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/homeport/dyff/pkg/dyff\"\n\t\"github.com/lucasb-eyer/go-colorful\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/util/errors\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/cli-utils/pkg/kstatus/polling\"\n\t\"github.com/fluxcd/cli-utils/pkg/object\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/ssa\"\n\t\"github.com/fluxcd/pkg/ssa/normalize\"\n\tssautil \"github.com/fluxcd/pkg/ssa/utils\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/printers\"\n)\n\nfunc (b *Builder) Manager() (*ssa.ResourceManager, error) {\n\tstatusPoller := polling.NewStatusPoller(b.client, b.restMapper, polling.Options{})\n\towner := ssa.Owner{\n\t\tField: controllerName,\n\t\tGroup: controllerGroup,\n\t}\n\n\treturn ssa.NewResourceManager(b.client, statusPoller, owner), nil\n}\n\nfunc (b *Builder) Diff() (string, bool, error) {\n\terr := b.StartSpinner()\n\tif err != nil {\n\t\treturn \"\", false, err\n\t}\n\n\toutput, createdOrDrifted, diffErr := b.diff()\n\n\terr = b.StopSpinner()\n\tif err != nil {\n\t\treturn \"\", false, err\n\t}\n\n\treturn output, createdOrDrifted, diffErr\n}\n\nfunc (b *Builder) diff() (string, bool, error) {\n\toutput := strings.Builder{}\n\tcreatedOrDrifted := false\n\tobjects, err := b.Build()\n\tif err != nil {\n\t\treturn \"\", createdOrDrifted, err\n\t}\n\n\terr = normalize.UnstructuredList(objects)\n\tif err != nil {\n\t\treturn \"\", createdOrDrifted, err\n\t}\n\n\tresourceManager, err := b.Manager()\n\tif err != nil {\n\t\treturn \"\", createdOrDrifted, err\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), b.timeout)\n\tdefer cancel()\n\n\tvar diffErrs []error\n\t// create an inventory of objects to be reconciled\n\tnewInventory := newInventory()\n\tfor _, obj := range objects {\n\t\tdiffOptions := ssa.DiffOptions{\n\t\t\tExclusions: map[string]string{\n\t\t\t\t\"kustomize.toolkit.fluxcd.io/reconcile\": \"disabled\",\n\t\t\t\t\"kustomize.toolkit.fluxcd.io/ssa\":       \"ignore\",\n\t\t\t},\n\t\t\tIfNotPresentSelector: map[string]string{\n\t\t\t\t\"kustomize.toolkit.fluxcd.io/ssa\": \"ifnotpresent\",\n\t\t\t},\n\t\t\tForceSelector: map[string]string{\n\t\t\t\t\"kustomize.toolkit.fluxcd.io/force\": \"enabled\",\n\t\t\t},\n\t\t}\n\t\tchange, liveObject, mergedObject, err := resourceManager.Diff(ctx, obj, diffOptions)\n\t\tif err != nil {\n\t\t\t// gather errors and continue, as we want to see all the diffs\n\t\t\tdiffErrs = append(diffErrs, err)\n\t\t\tcontinue\n\t\t}\n\t\tif change.Action == ssa.SkippedAction {\n\t\t\toutput.WriteString(writeString(fmt.Sprintf(\"► %s skipped\\n\", change.Subject), bunt.Orange))\n\t\t}\n\n\t\t// if the object is a sops secret, we need to\n\t\t// make sure we diff only if the keys are different\n\t\tif obj.GetKind() == \"Secret\" && change.Action == ssa.ConfiguredAction {\n\t\t\tdiffSopsSecret(obj, liveObject, mergedObject, change)\n\t\t}\n\n\t\tif change.Action == ssa.CreatedAction {\n\t\t\toutput.WriteString(writeString(fmt.Sprintf(\"► %s created\\n\", change.Subject), bunt.Green))\n\t\t\tcreatedOrDrifted = true\n\t\t}\n\n\t\tif change.Action == ssa.ConfiguredAction {\n\t\t\toutput.WriteString(bunt.Sprint(fmt.Sprintf(\"► %s drifted\\n\", change.Subject)))\n\t\t\tliveFile, mergedFile, tmpDir, err := writeYamls(liveObject, mergedObject)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", createdOrDrifted, err\n\t\t\t}\n\n\t\t\terr = diff(liveFile, mergedFile, &output)\n\t\t\tif err != nil {\n\t\t\t\tcleanupDir(tmpDir)\n\t\t\t\treturn \"\", createdOrDrifted, err\n\t\t\t}\n\n\t\t\tcleanupDir(tmpDir)\n\t\t\tcreatedOrDrifted = true\n\t\t}\n\n\t\taddObjectsToInventory(newInventory, change)\n\n\t\tif b.recursive && isKustomization(obj) && change.Action != ssa.CreatedAction {\n\t\t\tk, err := toKustomization(obj)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", createdOrDrifted, err\n\t\t\t}\n\n\t\t\tif !kustomizationsEqual(k, b.kustomization) {\n\t\t\t\tif k.Spec.KubeConfig != nil {\n\t\t\t\t\toutput.WriteString(writeString(fmt.Sprintf(\"⚠️ %s skipped: diff not supported for remote clusters\\n\", ssautil.FmtUnstructured(obj)), bunt.Orange))\n\t\t\t\t} else {\n\t\t\t\t\tsubOutput, subCreatedOrDrifted, err := b.kustomizationDiff(k)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tdiffErrs = append(diffErrs, err)\n\t\t\t\t\t}\n\t\t\t\t\tif subCreatedOrDrifted {\n\t\t\t\t\t\tcreatedOrDrifted = true\n\t\t\t\t\t\toutput.WriteString(bunt.Sprint(fmt.Sprintf(\"📁 %s changed\\n\", ssautil.FmtUnstructured(obj))))\n\t\t\t\t\t\toutput.WriteString(subOutput)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// finished with Kustomization diff\n\t\t\t\tif b.spinner != nil {\n\t\t\t\t\tb.spinner.Message(spinnerDryRunMessage)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif b.spinner != nil {\n\t\tb.spinner.Message(\"processing inventory\")\n\t}\n\n\tif b.kustomization.Spec.Prune && len(diffErrs) == 0 {\n\t\toldStatus := b.kustomization.Status.DeepCopy()\n\t\tif oldStatus.Inventory != nil {\n\t\t\tstaleObjects, err := diffInventory(oldStatus.Inventory, newInventory)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", createdOrDrifted, err\n\t\t\t}\n\t\t\tif len(staleObjects) > 0 {\n\t\t\t\tcreatedOrDrifted = true\n\t\t\t}\n\t\t\tfor _, object := range staleObjects {\n\t\t\t\toutput.WriteString(writeString(fmt.Sprintf(\"► %s deleted\\n\", ssautil.FmtUnstructured(object)), bunt.OrangeRed))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn output.String(), createdOrDrifted, errors.Reduce(errors.Flatten(errors.NewAggregate(diffErrs)))\n}\n\nfunc (b *Builder) kustomizationDiff(kustomization *kustomizev1.Kustomization) (string, bool, error) {\n\tif b.spinner != nil {\n\t\tb.spinner.Message(fmt.Sprintf(\"%s in %s\", spinnerDryRunMessage, kustomization.Name))\n\t}\n\n\tsourceRef := kustomization.Spec.SourceRef.DeepCopy()\n\tif sourceRef.Namespace == \"\" {\n\t\tsourceRef.Namespace = kustomization.Namespace\n\t}\n\n\tsourceKey := sourceRef.String()\n\tlocalPath, ok := b.localSources[sourceKey]\n\tif !ok {\n\t\treturn \"\", false, fmt.Errorf(\"cannot get local path for %s of kustomization %s\", sourceKey, kustomization.Name)\n\t}\n\n\tresourcesPath := filepath.Join(localPath, kustomization.Spec.Path)\n\tsubBuilder, err := NewBuilder(kustomization.Name, resourcesPath,\n\t\t// use same client and spinner\n\t\twithClientConfigFrom(b),\n\t\twithSpinnerFrom(b),\n\t\tWithTimeout(b.timeout),\n\t\tWithNamespace(kustomization.Namespace),\n\t\tWithIgnore(b.ignore),\n\t\tWithStrictSubstitute(b.strictSubst),\n\t\tWithRecursive(b.recursive),\n\t\tWithLocalSources(b.localSources),\n\t\tWithSingleKustomization(),\n\t)\n\tif err != nil {\n\t\treturn \"\", false, err\n\t}\n\n\treturn subBuilder.diff()\n}\n\nfunc writeYamls(liveObject, mergedObject *unstructured.Unstructured) (string, string, string, error) {\n\ttmpDir, err := os.MkdirTemp(\"\", \"\")\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", err\n\t}\n\n\tliveYAML, _ := yaml.Marshal(liveObject)\n\tliveFile := filepath.Join(tmpDir, \"live.yaml\")\n\tif err := os.WriteFile(liveFile, liveYAML, 0o600); err != nil {\n\t\treturn \"\", \"\", \"\", err\n\t}\n\n\tmergedYAML, _ := yaml.Marshal(mergedObject)\n\tmergedFile := filepath.Join(tmpDir, \"merged.yaml\")\n\tif err := os.WriteFile(mergedFile, mergedYAML, 0o600); err != nil {\n\t\treturn \"\", \"\", \"\", err\n\t}\n\n\treturn liveFile, mergedFile, tmpDir, nil\n}\n\nfunc writeString(t string, color colorful.Color) string {\n\treturn bunt.Style(\n\t\tt,\n\t\tbunt.EachLine(),\n\t\tbunt.Foreground(color),\n\t)\n}\n\nfunc cleanupDir(dir string) error {\n\treturn os.RemoveAll(dir)\n}\n\nfunc diff(liveFile, mergedFile string, output io.Writer) error {\n\tfrom, to, err := ytbx.LoadFiles(liveFile, mergedFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load input files: %w\", err)\n\t}\n\n\treport, err := dyff.CompareInputFiles(from, to,\n\t\tdyff.IgnoreOrderChanges(false),\n\t\tdyff.KubernetesEntityDetection(true),\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to compare input files: %w\", err)\n\t}\n\n\tprinter := printers.NewDyffPrinter()\n\n\tprinter.Print(output, report)\n\n\treturn nil\n}\n\nfunc diffSopsSecret(obj, liveObject, mergedObject *unstructured.Unstructured, change *ssa.ChangeSetEntry) {\n\t// get both data and stringdata maps\n\tdata := obj.Object[dataField]\n\n\tif m, ok := data.(map[string]interface{}); ok && m != nil {\n\t\tapplySopsDiff(m, liveObject, mergedObject, change)\n\t}\n}\n\nfunc applySopsDiff(data map[string]interface{}, liveObject, mergedObject *unstructured.Unstructured, change *ssa.ChangeSetEntry) {\n\tfor _, v := range data {\n\t\tv, err := base64.StdEncoding.DecodeString(v.(string))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\n\t\tif bytes.Contains(v, []byte(mask)) {\n\t\t\tif liveObject != nil && mergedObject != nil {\n\t\t\t\tchange.Action = ssa.UnchangedAction\n\t\t\t\tliveKeys, mergedKeys := sopsComparableByKeys(liveObject), sopsComparableByKeys(mergedObject)\n\t\t\t\tif cmp.Diff(liveKeys, mergedKeys) != \"\" {\n\t\t\t\t\tchange.Action = ssa.ConfiguredAction\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc sopsComparableByKeys(object *unstructured.Unstructured) []string {\n\tm := object.Object[dataField].(map[string]interface{})\n\tkeys := make([]string, len(m))\n\ti := 0\n\tfor k := range m {\n\t\t// make sure we can compare only on keys\n\t\tm[k] = \"*****\"\n\t\tkeys[i] = k\n\t\ti++\n\t}\n\n\tobject.Object[dataField] = m\n\n\tsort.Strings(keys)\n\n\treturn keys\n}\n\n// diffInventory returns the slice of objects that do not exist in the target inventory.\nfunc diffInventory(inv *kustomizev1.ResourceInventory, target *kustomizev1.ResourceInventory) ([]*unstructured.Unstructured, error) {\n\tversionOf := func(i *kustomizev1.ResourceInventory, objMetadata object.ObjMetadata) string {\n\t\tfor _, entry := range i.Entries {\n\t\t\tif entry.ID == objMetadata.String() {\n\t\t\t\treturn entry.Version\n\t\t\t}\n\t\t}\n\t\treturn \"\"\n\t}\n\n\tobjects := make([]*unstructured.Unstructured, 0)\n\taList, err := listMetaInInventory(inv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbList, err := listMetaInInventory(target)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlist := aList.Diff(bList)\n\tif len(list) == 0 {\n\t\treturn objects, nil\n\t}\n\n\tfor _, metadata := range list {\n\t\tu := &unstructured.Unstructured{}\n\t\tu.SetGroupVersionKind(schema.GroupVersionKind{\n\t\t\tGroup:   metadata.GroupKind.Group,\n\t\t\tKind:    metadata.GroupKind.Kind,\n\t\t\tVersion: versionOf(inv, metadata),\n\t\t})\n\t\tu.SetName(metadata.Name)\n\t\tu.SetNamespace(metadata.Namespace)\n\t\tobjects = append(objects, u)\n\t}\n\n\tsort.Sort(ssa.SortableUnstructureds(objects))\n\treturn objects, nil\n}\n\n// listMetaInInventory returns the inventory entries as object.ObjMetadata objects.\nfunc listMetaInInventory(inv *kustomizev1.ResourceInventory) (object.ObjMetadataSet, error) {\n\tvar metas []object.ObjMetadata\n\tfor _, e := range inv.Entries {\n\t\tm, err := object.ParseObjMetadata(e.ID)\n\t\tif err != nil {\n\t\t\treturn metas, err\n\t\t}\n\t\tmetas = append(metas, m)\n\t}\n\n\treturn metas, nil\n}\n\nfunc newInventory() *kustomizev1.ResourceInventory {\n\treturn &kustomizev1.ResourceInventory{\n\t\tEntries: []kustomizev1.ResourceRef{},\n\t}\n}\n\n// addObjectsToInventory extracts the metadata from the given objects and adds it to the inventory.\nfunc addObjectsToInventory(inv *kustomizev1.ResourceInventory, entry *ssa.ChangeSetEntry) error {\n\tif entry == nil {\n\t\treturn nil\n\t}\n\n\tinv.Entries = append(inv.Entries, kustomizev1.ResourceRef{\n\t\tID:      entry.ObjMetadata.String(),\n\t\tVersion: entry.GroupVersion,\n\t})\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/build/testdata/local-kustomization/different-name.yaml",
    "content": "apiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: infra\n  namespace: flux-system\nspec:\n  path: \"./clusters/test-build\"\n"
  },
  {
    "path": "internal/build/testdata/local-kustomization/invalid-resource.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: podinfo\n"
  },
  {
    "path": "internal/build/testdata/local-kustomization/multi-doc-reset.yaml",
    "content": "apiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: first\n  namespace: flux-system\nspec:\n  path: \"./k8s/first\"\n  components:\n  - foo\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  path: \"./k8s/second\"\n---\n"
  },
  {
    "path": "internal/build/testdata/local-kustomization/multi-doc-valid.yaml",
    "content": "---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: infra-namespace\n  namespace: flux-system\n  labels:\n    component.kutara.io/part-of: definitions\nspec:\n  path: \"./k8s/base/infra\"\n  prune: true\n---\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 30s\n  ref:\n    branch: master\n  url: https://github.com/stefanprodan/podinfo\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  path: \"./clusters/test-build\"\n---\n"
  },
  {
    "path": "internal/build/testdata/local-kustomization/no-ns.yaml",
    "content": "apiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: podinfo\nspec:\n  path: \"./clusters/test-build\"\n"
  },
  {
    "path": "internal/build/testdata/local-kustomization/valid.yaml",
    "content": "apiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  path: \"./clusters/test-build\"\n"
  },
  {
    "path": "internal/flags/crds.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar supportedCRDsPolicies = []string{\n\tstring(helmv2.Skip),\n\tstring(helmv2.Create),\n\tstring(helmv2.CreateReplace),\n}\n\ntype CRDsPolicy string\n\nfunc (a *CRDsPolicy) String() string {\n\treturn string(*a)\n}\n\nfunc (a *CRDsPolicy) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\treturn fmt.Errorf(\"no upgrade CRDs policy given, must be one of: %s\",\n\t\t\tstrings.Join(supportedCRDsPolicies, \", \"))\n\t}\n\tif !utils.ContainsItemString(supportedCRDsPolicies, str) {\n\t\treturn fmt.Errorf(\"unsupported upgrade CRDs policy '%s', must be one of: %s\",\n\t\t\tstr, strings.Join(supportedCRDsPolicies, \", \"))\n\n\t}\n\t*a = CRDsPolicy(str)\n\treturn nil\n}\n\nfunc (a *CRDsPolicy) Type() string {\n\treturn strings.Join(supportedCRDsPolicies, \"|\")\n}\n\nfunc (a *CRDsPolicy) Description() string {\n\treturn \"upgrade CRDs policy\"\n}\n"
  },
  {
    "path": "internal/flags/crds_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCRDsPolicy_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"supported\", \"CreateReplace\", \"CreateReplace\", false},\n\t\t{\"unsupported\", \"createreplace\", \"\", true},\n\t\t{\"empty\", \"\", \"\", true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar a CRDsPolicy\n\t\t\tif err := a.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := a.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/flags/decryption_provider.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar supportedDecryptionProviders = []string{\"sops\"}\n\ntype DecryptionProvider string\n\nfunc (d *DecryptionProvider) String() string {\n\treturn string(*d)\n}\n\nfunc (d *DecryptionProvider) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\treturn fmt.Errorf(\"no decryption provider given, must be one of: %s\",\n\t\t\tstrings.Join(supportedDecryptionProviders, \", \"))\n\t}\n\tif !utils.ContainsItemString(supportedDecryptionProviders, str) {\n\t\treturn fmt.Errorf(\"unsupported decryption provider '%s', must be one of: %s\",\n\t\t\tstr, strings.Join(supportedDecryptionProviders, \", \"))\n\n\t}\n\t*d = DecryptionProvider(str)\n\treturn nil\n}\n\nfunc (d *DecryptionProvider) Type() string {\n\treturn \"decryptionProvider\"\n}\n\nfunc (d *DecryptionProvider) Description() string {\n\treturn fmt.Sprintf(\"decryption provider, available options are: (%s)\", strings.Join(supportedDecryptionProviders, \", \"))\n}\n"
  },
  {
    "path": "internal/flags/decryption_provider_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"testing\"\n)\n\nfunc TestDecryptionProvider_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"supported\", \"sops\", \"sops\", false},\n\t\t{\"unsupported\", \"unsupported\", \"\", true},\n\t\t{\"empty\", \"\", \"\", true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar p DecryptionProvider\n\t\t\tif err := p.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := p.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/flags/ecdsa_curve.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"crypto/elliptic\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n)\n\ntype ECDSACurve struct {\n\telliptic.Curve\n}\n\nvar supportedECDSACurves = map[string]elliptic.Curve{\n\t\"p256\": elliptic.P256(),\n\t\"p384\": elliptic.P384(),\n\t\"p521\": elliptic.P521(),\n}\n\nfunc (c *ECDSACurve) String() string {\n\tif c.Curve == nil {\n\t\treturn \"\"\n\t}\n\treturn strings.ToLower(strings.Replace(c.Curve.Params().Name, \"-\", \"\", 1))\n}\n\nfunc (c *ECDSACurve) Set(str string) error {\n\tif v, ok := supportedECDSACurves[str]; ok {\n\t\t*c = ECDSACurve{v}\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unsupported curve '%s', must be one of: %s\", str, strings.Join(ecdsaCurves(), \", \"))\n}\n\nfunc (c *ECDSACurve) Type() string {\n\tkeys := make([]string, 0, len(supportedECDSACurves))\n\tfor k := range supportedECDSACurves {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\treturn strings.Join(keys, \"|\")\n}\n\nfunc (c *ECDSACurve) Description() string {\n\treturn \"SSH ECDSA public key curve\"\n}\n\nfunc ecdsaCurves() []string {\n\tkeys := make([]string, 0, len(supportedECDSACurves))\n\tfor k := range supportedECDSACurves {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\treturn keys\n}\n"
  },
  {
    "path": "internal/flags/ecdsa_curve_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"testing\"\n)\n\nfunc TestECDSACurve_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"supported\", \"p256\", \"p256\", false},\n\t\t{\"unsupported\", \"unsupported\", \"\", true},\n\t\t{\"empty\", \"\", \"\", true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar c ECDSACurve\n\t\t\tif err := c.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := c.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/flags/gitlab_visibility.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fluxcd/go-git-providers/gitprovider\"\n\t\"github.com/fluxcd/go-git-providers/validation\"\n)\n\nvar supportedGitLabVisibilities = map[gitprovider.RepositoryVisibility]struct{}{\n\tgitprovider.RepositoryVisibilityPublic:   {},\n\tgitprovider.RepositoryVisibilityInternal: {},\n\tgitprovider.RepositoryVisibilityPrivate:  {},\n}\n\n// ValidateRepositoryVisibility validates a given RepositoryVisibility.\nfunc ValidateRepositoryVisibility(r gitprovider.RepositoryVisibility) error {\n\t_, ok := supportedGitLabVisibilities[r]\n\tif !ok {\n\t\treturn validation.ErrFieldEnumInvalid\n\t}\n\treturn nil\n}\n\ntype GitLabVisibility gitprovider.RepositoryVisibility\n\nfunc (d *GitLabVisibility) String() string {\n\treturn string(*d)\n}\n\nfunc (d *GitLabVisibility) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\tstr = string(gitprovider.RepositoryVisibilityPrivate)\n\t}\n\tvar visibility = gitprovider.RepositoryVisibility(str)\n\tif ValidateRepositoryVisibility(visibility) != nil {\n\t\treturn fmt.Errorf(\"unsupported visibility '%s'\", str)\n\t}\n\t*d = GitLabVisibility(visibility)\n\treturn nil\n}\n\nfunc (d *GitLabVisibility) Type() string {\n\tkeys := make([]string, 0, len(supportedGitLabVisibilities))\n\tfor v := range supportedGitLabVisibilities {\n\t\tkeys = append(keys, string(v))\n\t}\n\treturn strings.Join(keys, \"|\")\n}\n\nfunc (d *GitLabVisibility) Description() string {\n\treturn \"specifies the visibility of the repository\"\n}\n"
  },
  {
    "path": "internal/flags/gitlab_visibility_test.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"testing\"\n)\n\nfunc TestGitLabVisibility_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"private\", \"private\", \"private\", false},\n\t\t{\"internal\", \"internal\", \"internal\", false},\n\t\t{\"public\", \"public\", \"public\", false},\n\t\t{\"unsupported\", \"unsupported\", \"\", true},\n\t\t{\"default\", \"\", \"private\", false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar p GitLabVisibility\n\t\t\tif err := p.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := p.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/flags/helm_chart_source.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar supportedHelmChartSourceKinds = []string{sourcev1.HelmRepositoryKind, sourcev1.GitRepositoryKind, sourcev1.BucketKind}\n\ntype HelmChartSource struct {\n\tKind      string\n\tName      string\n\tNamespace string\n}\n\nfunc (s *HelmChartSource) String() string {\n\tif s.Name == \"\" {\n\t\treturn \"\"\n\t}\n\treturn fmt.Sprintf(\"%s/%s\", s.Kind, s.Name)\n}\n\nfunc (s *HelmChartSource) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\treturn fmt.Errorf(\"no helm chart source given, please specify %s\",\n\t\t\ts.Description())\n\t}\n\n\tsourceKind, sourceName, sourceNamespace := utils.ParseObjectKindNameNamespace(str)\n\tif sourceKind == \"\" || sourceName == \"\" {\n\t\treturn fmt.Errorf(\"invalid helm chart source '%s', must be in format <kind>/<name>\", str)\n\t}\n\tcleanSourceKind, ok := utils.ContainsEqualFoldItemString(supportedHelmChartSourceKinds, sourceKind)\n\tif !ok {\n\t\treturn fmt.Errorf(\"source kind '%s' is not supported, must be one of: %s\",\n\t\t\tsourceKind, strings.Join(supportedHelmChartSourceKinds, \", \"))\n\t}\n\n\ts.Kind = cleanSourceKind\n\ts.Name = sourceName\n\ts.Namespace = sourceNamespace\n\n\treturn nil\n}\n\nfunc (s *HelmChartSource) Type() string {\n\treturn \"string\"\n}\n\nfunc (s *HelmChartSource) Description() string {\n\treturn fmt.Sprintf(\n\t\t\"source that contains the chart in the format '<kind>/<name>.<namespace>', \"+\n\t\t\t\"where kind must be one of: (%s)\",\n\t\tstrings.Join(supportedHelmChartSourceKinds, \", \"),\n\t)\n}\n"
  },
  {
    "path": "internal/flags/helm_chart_source_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nfunc TestHelmChartSource_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"supported\", fmt.Sprintf(\"%s/foo\", sourcev1.HelmRepositoryKind), fmt.Sprintf(\"%s/foo\", sourcev1.HelmRepositoryKind), false},\n\t\t{\"lower case kind\", \"helmrepository/foo\", fmt.Sprintf(\"%s/foo\", sourcev1.HelmRepositoryKind), false},\n\t\t{\"unsupported\", \"Unsupported/kind\", \"\", true},\n\t\t{\"invalid format\", sourcev1.HelmRepositoryKind, \"\", true},\n\t\t{\"missing name\", fmt.Sprintf(\"%s/\", sourcev1.HelmRepositoryKind), \"\", true},\n\t\t{\"empty\", \"\", \"\", true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar s HelmChartSource\n\t\t\tif err := s.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := s.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/flags/kustomization_source.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar supportedKustomizationSourceKinds = []string{sourcev1.OCIRepositoryKind, sourcev1.GitRepositoryKind, sourcev1.BucketKind}\n\ntype KustomizationSource struct {\n\tKind      string\n\tName      string\n\tNamespace string\n}\n\nfunc (s *KustomizationSource) String() string {\n\tif s.Name == \"\" {\n\t\treturn \"\"\n\t}\n\treturn fmt.Sprintf(\"%s/%s\", s.Kind, s.Name)\n}\n\nfunc (s *KustomizationSource) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\treturn fmt.Errorf(\"no Kustomization source given, please specify %s\",\n\t\t\ts.Description())\n\t}\n\n\tsourceKind, sourceName, sourceNamespace := utils.ParseObjectKindNameNamespace(str)\n\tif sourceName == \"\" {\n\t\treturn fmt.Errorf(\"no name given for source of kind '%s'\", sourceKind)\n\t}\n\tif sourceKind == \"\" {\n\t\tif utils.ContainsItemString(supportedKustomizationSourceKinds, sourceName) {\n\t\t\treturn fmt.Errorf(\"no kind specified for source '%s'\", sourceName)\n\t\t}\n\t\tsourceKind = sourcev1.GitRepositoryKind\n\t}\n\tcleanSourceKind, ok := utils.ContainsEqualFoldItemString(supportedKustomizationSourceKinds, sourceKind)\n\tif !ok {\n\t\treturn fmt.Errorf(\"source kind '%s' is not supported, must be one of: %s\",\n\t\t\tsourceKind, strings.Join(supportedKustomizationSourceKinds, \", \"))\n\t}\n\n\ts.Kind = cleanSourceKind\n\ts.Name = sourceName\n\ts.Namespace = sourceNamespace\n\n\treturn nil\n}\n\nfunc (s *KustomizationSource) Type() string {\n\treturn \"string\"\n}\n\nfunc (s *KustomizationSource) Description() string {\n\treturn fmt.Sprintf(\n\t\t\"source that contains the Kubernetes manifests in the format '[<kind>/]<name>.<namespace>', \"+\n\t\t\t\"where kind must be one of: (%s), if kind is not specified it defaults to GitRepository\",\n\t\tstrings.Join(supportedKustomizationSourceKinds, \", \"),\n\t)\n}\n"
  },
  {
    "path": "internal/flags/kustomization_source_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nfunc TestKustomizationSource_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"supported\", fmt.Sprintf(\"%s/foo\", sourcev1.GitRepositoryKind), fmt.Sprintf(\"%s/foo\", sourcev1.GitRepositoryKind), false},\n\t\t{\"default kind\", \"foo\", fmt.Sprintf(\"%s/foo\", sourcev1.GitRepositoryKind), false},\n\t\t{\"lower case kind\", \"gitrepository/foo\", fmt.Sprintf(\"%s/foo\", sourcev1.GitRepositoryKind), false},\n\t\t{\"unsupported\", \"Unsupported/kind\", \"\", true},\n\t\t{\"missing name\", fmt.Sprintf(\"%s/\", sourcev1.GitRepositoryKind), \"\", true},\n\t\t{\"empty\", \"\", \"\", true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar s KustomizationSource\n\t\t\tif err := s.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := s.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/flags/local_helm_chart_source.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\ntype LocalHelmChartSource struct {\n\tKind string\n\tName string\n}\n\nfunc (s *LocalHelmChartSource) String() string {\n\tif s.Name == \"\" {\n\t\treturn \"\"\n\t}\n\treturn fmt.Sprintf(\"%s/%s\", s.Kind, s.Name)\n}\n\nfunc (s *LocalHelmChartSource) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\treturn fmt.Errorf(\"no helm chart source given, please specify %s\",\n\t\t\ts.Description())\n\t}\n\n\tsourceKind, sourceName := utils.ParseObjectKindName(str)\n\tif sourceKind == \"\" || sourceName == \"\" {\n\t\treturn fmt.Errorf(\"invalid helm chart source '%s', must be in format <kind>/<name>\", str)\n\t}\n\tcleanSourceKind, ok := utils.ContainsEqualFoldItemString(supportedHelmChartSourceKinds, sourceKind)\n\tif !ok {\n\t\treturn fmt.Errorf(\"source kind '%s' is not supported, must be one of: %s\",\n\t\t\tsourceKind, strings.Join(supportedHelmChartSourceKinds, \", \"))\n\t}\n\n\ts.Kind = cleanSourceKind\n\ts.Name = sourceName\n\n\treturn nil\n}\n\nfunc (s *LocalHelmChartSource) Type() string {\n\treturn \"helmChartSource\"\n}\n\nfunc (s *LocalHelmChartSource) Description() string {\n\treturn fmt.Sprintf(\n\t\t\"source that contains the chart in the format '<kind>/<name>', \"+\n\t\t\t\"where kind must be one of: (%s)\",\n\t\tstrings.Join(supportedHelmChartSourceKinds, \", \"),\n\t)\n}\n"
  },
  {
    "path": "internal/flags/log_level.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar supportedLogLevels = []string{\"debug\", \"info\", \"error\"}\n\ntype LogLevel string\n\nfunc (l *LogLevel) String() string {\n\treturn string(*l)\n}\n\nfunc (l *LogLevel) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\treturn fmt.Errorf(\"no log level given, must be one of: %s\",\n\t\t\tstrings.Join(supportedLogLevels, \", \"))\n\t}\n\tif !utils.ContainsItemString(supportedLogLevels, str) {\n\t\treturn fmt.Errorf(\"unsupported log level '%s', must be one of: %s\",\n\t\t\tstr, strings.Join(supportedLogLevels, \", \"))\n\n\t}\n\t*l = LogLevel(str)\n\treturn nil\n}\n\nfunc (l *LogLevel) Type() string {\n\treturn strings.Join(supportedLogLevels, \"|\")\n}\n\nfunc (l *LogLevel) Description() string {\n\treturn \"log level\"\n}\n"
  },
  {
    "path": "internal/flags/log_level_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"testing\"\n)\n\nfunc TestLogLevel_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"supported\", \"info\", \"info\", false},\n\t\t{\"unsupported\", \"unsupported\", \"\", true},\n\t\t{\"empty\", \"\", \"\", true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar l LogLevel\n\t\t\tif err := l.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := l.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/flags/public_key_algorithm.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\nvar supportedPublicKeyAlgorithms = []string{\"rsa\", \"ecdsa\", \"ed25519\"}\n\ntype PublicKeyAlgorithm string\n\nfunc (a *PublicKeyAlgorithm) String() string {\n\treturn string(*a)\n}\n\nfunc (a *PublicKeyAlgorithm) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\treturn fmt.Errorf(\"no public key algorithm given, must be one of: %s\",\n\t\t\tstrings.Join(supportedPublicKeyAlgorithms, \", \"))\n\t}\n\tfor _, v := range supportedPublicKeyAlgorithms {\n\t\tif str == v {\n\t\t\t*a = PublicKeyAlgorithm(str)\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"unsupported public key algorithm '%s', must be one of: %s\",\n\t\tstr, strings.Join(supportedPublicKeyAlgorithms, \", \"))\n}\n\nfunc (a *PublicKeyAlgorithm) Type() string {\n\treturn strings.Join(supportedPublicKeyAlgorithms, \"|\")\n}\n\nfunc (a *PublicKeyAlgorithm) Description() string {\n\treturn \"SSH public key algorithm\"\n}\n"
  },
  {
    "path": "internal/flags/public_key_algorithm_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"testing\"\n)\n\nfunc TestPublicKeyAlgorithm_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"supported\", \"rsa\", \"rsa\", false},\n\t\t{\"unsupported\", \"unsupported\", \"\", true},\n\t\t{\"empty\", \"\", \"\", true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar a PublicKeyAlgorithm\n\t\t\tif err := a.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := a.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/flags/rsa_key_bits.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar defaultRSAKeyBits = 2048\n\ntype RSAKeyBits int\n\nfunc (b *RSAKeyBits) String() string {\n\treturn strconv.Itoa(int(*b))\n}\n\nfunc (b *RSAKeyBits) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\t*b = RSAKeyBits(defaultRSAKeyBits)\n\t\treturn nil\n\t}\n\tbits, err := strconv.Atoi(str)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif bits < 1024 {\n\t\treturn fmt.Errorf(\"RSA key bit size must be at least 1024\")\n\t}\n\tif bits == 0 || bits%8 != 0 {\n\t\treturn fmt.Errorf(\"RSA key bit size must be a multiples of 8\")\n\t}\n\t*b = RSAKeyBits(bits)\n\treturn nil\n}\n\nfunc (b *RSAKeyBits) Type() string {\n\treturn \"rsaKeyBits\"\n}\n\nfunc (b *RSAKeyBits) Description() string {\n\treturn \"SSH RSA public key bit size (multiplies of 8, min 1024)\"\n}\n"
  },
  {
    "path": "internal/flags/rsa_key_bits_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"testing\"\n)\n\nfunc TestRSAKeyBits_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"supported\", \"4096\", \"4096\", false},\n\t\t{\"empty (default)\", \"\", \"2048\", false},\n\t\t{\"unsupported\", \"512\", \"0\", true},\n\t\t{\"unsupported\", \"1025\", \"0\", true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar b RSAKeyBits\n\t\t\tif err := b.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := b.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/flags/safe_relative_path.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\tsecurejoin \"github.com/cyphar/filepath-securejoin\"\n)\n\ntype SafeRelativePath string\n\nfunc (p *SafeRelativePath) String() string {\n\treturn string(*p)\n}\n\nfunc (p *SafeRelativePath) ToSlash() string {\n\treturn filepath.ToSlash(p.String())\n}\n\nfunc (p *SafeRelativePath) Set(str string) error {\n\t// The result of secure joining on a relative base dir is a flattened relative path.\n\tcleanP, err := securejoin.SecureJoin(\"./\", strings.TrimSpace(str))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid relative path '%s': %w\", cleanP, err)\n\t}\n\t// NB: required, as a secure join of \"./\" will result in \".\"\n\tif cleanP == \".\" {\n\t\tcleanP = \"\"\n\t}\n\tcleanP = fmt.Sprintf(\"./%s\", cleanP)\n\t*p = SafeRelativePath(cleanP)\n\treturn nil\n}\n\nfunc (p *SafeRelativePath) Type() string {\n\treturn \"safeRelativePath\"\n}\n\nfunc (p *SafeRelativePath) Description() string {\n\treturn \"secure relative path\"\n}\n"
  },
  {
    "path": "internal/flags/safe_relative_path_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"testing\"\n)\n\nfunc TestRelativePath_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"relative path\", \"./foo\", \"./foo\", false},\n\t\t{\"relative path\", \"foo\", \"./foo\", false},\n\t\t{\"traversing relative path\", \"./foo/../bar\", \"./bar\", false},\n\t\t{\"absolute path\", \"/foo\", \"./foo\", false},\n\t\t{\"traversing absolute path\", \"/foo/../bar\", \"./bar\", false},\n\t\t{\"traversing overflowing absolute path\", \"/foo/../../../bar\", \"./bar\", false},\n\t\t{\"empty\", \"\", \"./\", false},\n\t\t{\"relative empty path\", \"./\", \"./\", false},\n\t\t{\"double relative empty path\", \"././\", \"./\", false},\n\t\t{\"dot path\", \".foo\", \"./.foo\", false},\n\t\t{\"relative dot path\", \"./.foo\", \"./.foo\", false},\n\t\t{\"current directory\", \".\", \"./\", false},\n\t\t{\"parent directory\", \"..\", \"./\", false},\n\t\t{\"parent directory more qualified\", \"./..\", \"./\", false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar p SafeRelativePath\n\t\t\tif err := p.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := p.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/flags/source_bucket_provider.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar supportedSourceBucketProviders = []string{\n\tsourcev1.BucketProviderGeneric,\n\tsourcev1.BucketProviderAmazon,\n\tsourcev1.BucketProviderAzure,\n\tsourcev1.BucketProviderGoogle,\n}\n\ntype SourceBucketProvider string\n\nfunc (p *SourceBucketProvider) String() string {\n\treturn string(*p)\n}\n\nfunc (p *SourceBucketProvider) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\treturn fmt.Errorf(\"no source bucket provider given, please specify %s\",\n\t\t\tp.Description())\n\t}\n\tif !utils.ContainsItemString(supportedSourceBucketProviders, str) {\n\t\treturn fmt.Errorf(\"source bucket provider '%s' is not supported, must be one of: %v\",\n\t\t\tstr, strings.Join(supportedSourceBucketProviders, \", \"))\n\t}\n\t*p = SourceBucketProvider(str)\n\treturn nil\n}\n\nfunc (p *SourceBucketProvider) Type() string {\n\treturn strings.Join(supportedSourceBucketProviders, \"|\")\n}\n\nfunc (p *SourceBucketProvider) Description() string {\n\treturn \"the S3 compatible storage provider name\"\n}\n"
  },
  {
    "path": "internal/flags/source_bucket_provider_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"testing\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nfunc TestSourceBucketProvider_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"supported\", sourcev1.BucketProviderGeneric, sourcev1.BucketProviderGeneric, false},\n\t\t{\"unsupported\", \"unsupported\", \"\", true},\n\t\t{\"empty\", \"\", \"\", true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar s SourceBucketProvider\n\t\t\tif err := s.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := s.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/flags/source_git_provider.go",
    "content": "/*\nCopyright 2024 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nvar supportedSourceGitProviders = []string{\n\tsourcev1.GitProviderGeneric,\n\tsourcev1.GitProviderAzure,\n\tsourcev1.GitProviderGitHub,\n}\n\ntype SourceGitProvider string\n\nfunc (p *SourceGitProvider) String() string {\n\treturn string(*p)\n}\n\nfunc (p *SourceGitProvider) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\treturn fmt.Errorf(\"no source Git provider given, please specify %s\",\n\t\t\tp.Description())\n\t}\n\tif !utils.ContainsItemString(supportedSourceGitProviders, str) {\n\t\treturn fmt.Errorf(\"source Git provider '%s' is not supported, must be one of: %v\",\n\t\t\tstr, p.Type())\n\t}\n\t*p = SourceGitProvider(str)\n\treturn nil\n}\n\nfunc (p *SourceGitProvider) Type() string {\n\treturn strings.Join(supportedSourceGitProviders, \"|\")\n}\n\nfunc (p *SourceGitProvider) Description() string {\n\treturn \"the Git provider name\"\n}\n"
  },
  {
    "path": "internal/flags/source_oci_provider.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar supportedSourceOCIProviders = []string{\n\tsourcev1.GenericOCIProvider,\n\tsourcev1.AmazonOCIProvider,\n\tsourcev1.AzureOCIProvider,\n\tsourcev1.GoogleOCIProvider,\n}\n\ntype SourceOCIProvider string\n\nfunc (p *SourceOCIProvider) String() string {\n\treturn string(*p)\n}\n\nfunc (p *SourceOCIProvider) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\treturn fmt.Errorf(\"no source OCI provider given, please specify %s\",\n\t\t\tp.Description())\n\t}\n\tif !utils.ContainsItemString(supportedSourceOCIProviders, str) {\n\t\treturn fmt.Errorf(\"source OCI provider '%s' is not supported, must be one of: %v\",\n\t\t\tstr, strings.Join(supportedSourceOCIProviders, \", \"))\n\t}\n\t*p = SourceOCIProvider(str)\n\treturn nil\n}\n\nfunc (p *SourceOCIProvider) Type() string {\n\treturn strings.Join(supportedSourceOCIProviders, \"|\")\n}\n\nfunc (p *SourceOCIProvider) Description() string {\n\treturn \"the OCI provider name\"\n}\n"
  },
  {
    "path": "internal/flags/source_oci_verify_provider.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nvar supportedSourceOCIVerifyProviders = []string{\n\t\"cosign\",\n}\n\ntype SourceOCIVerifyProvider string\n\nfunc (p *SourceOCIVerifyProvider) String() string {\n\treturn string(*p)\n}\n\nfunc (p *SourceOCIVerifyProvider) Set(str string) error {\n\tif strings.TrimSpace(str) == \"\" {\n\t\treturn fmt.Errorf(\"no source OCI verify provider given, please specify %s\",\n\t\t\tp.Description())\n\t}\n\tif !utils.ContainsItemString(supportedSourceOCIVerifyProviders, str) {\n\t\treturn fmt.Errorf(\"source OCI verify provider '%s' is not supported, must be one of: %v\",\n\t\t\tstr, strings.Join(supportedSourceOCIVerifyProviders, \", \"))\n\t}\n\t*p = SourceOCIVerifyProvider(str)\n\treturn nil\n}\n\nfunc (p *SourceOCIVerifyProvider) Type() string {\n\treturn \"sourceOCIVerifyProvider\"\n}\n\nfunc (p *SourceOCIVerifyProvider) Description() string {\n\treturn fmt.Sprintf(\n\t\t\"the OCI verify provider name to use for signature verification, available options are: (%s)\",\n\t\tstrings.Join(supportedSourceOCIVerifyProviders, \", \"),\n\t)\n}\n"
  },
  {
    "path": "internal/flags/source_oci_verify_provider_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage flags\n\nimport (\n\t\"testing\"\n)\n\nfunc TestSourceOCIVerifyProvider_Set(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tstr       string\n\t\texpect    string\n\t\texpectErr bool\n\t}{\n\t\t{\"supported\", \"cosign\", \"cosign\", false},\n\t\t{\"unsupported\", \"unsupported\", \"\", true},\n\t\t{\"empty\", \"\", \"\", true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar s SourceOCIVerifyProvider\n\t\t\tif err := s.Set(tt.str); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"Set() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t\tif str := s.String(); str != tt.expect {\n\t\t\t\tt.Errorf(\"Set() = %v, expect %v\", str, tt.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/tree/tree.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\nDerived work from https://github.com/d6o/GoTree\nCopyright (c) 2017 Diego Siqueira\n*/\n\npackage tree\n\nimport (\n\t\"strings\"\n\n\t\"github.com/fluxcd/cli-utils/pkg/object\"\n\tssautil \"github.com/fluxcd/pkg/ssa/utils\"\n)\n\nconst (\n\tnewLine      = \"\\n\"\n\temptySpace   = \"    \"\n\tmiddleItem   = \"├── \"\n\tcontinueItem = \"│   \"\n\tlastItem     = \"└── \"\n)\n\ntype (\n\tobjMetadataTree struct {\n\t\tResource     object.ObjMetadata `json:\"resource\"`\n\t\tResourceTree []ObjMetadataTree  `json:\"resources,omitempty\"`\n\t}\n\n\tObjMetadataTree interface {\n\t\tAdd(objMetadata object.ObjMetadata) ObjMetadataTree\n\t\tAddTree(tree ObjMetadataTree)\n\t\tItems() []ObjMetadataTree\n\t\tText() string\n\t\tPrint() string\n\t}\n\n\tprinter struct {\n\t}\n\n\tPrinter interface {\n\t\tPrint(ObjMetadataTree) string\n\t}\n)\n\nfunc New(objMetadata object.ObjMetadata) ObjMetadataTree {\n\treturn &objMetadataTree{\n\t\tResource:     objMetadata,\n\t\tResourceTree: []ObjMetadataTree{},\n\t}\n}\n\nfunc (t *objMetadataTree) Add(objMetadata object.ObjMetadata) ObjMetadataTree {\n\tn := New(objMetadata)\n\tt.ResourceTree = append(t.ResourceTree, n)\n\treturn n\n}\n\nfunc (t *objMetadataTree) AddTree(tree ObjMetadataTree) {\n\tt.ResourceTree = append(t.ResourceTree, tree)\n}\n\nfunc (t *objMetadataTree) Text() string {\n\treturn ssautil.FmtObjMetadata(t.Resource)\n}\n\nfunc (t *objMetadataTree) Items() []ObjMetadataTree {\n\treturn t.ResourceTree\n}\n\nfunc (t *objMetadataTree) Print() string {\n\treturn newPrinter().Print(t)\n}\n\nfunc newPrinter() Printer {\n\treturn &printer{}\n}\n\nfunc (p *printer) Print(t ObjMetadataTree) string {\n\treturn t.Text() + newLine + p.printItems(t.Items(), []bool{})\n}\n\nfunc (p *printer) printText(text string, spaces []bool, last bool) string {\n\tvar result string\n\tfor _, space := range spaces {\n\t\tif space {\n\t\t\tresult += emptySpace\n\t\t} else {\n\t\t\tresult += continueItem\n\t\t}\n\t}\n\n\tindicator := middleItem\n\tif last {\n\t\tindicator = lastItem\n\t}\n\n\tvar out string\n\tlines := strings.Split(text, \"\\n\")\n\tfor i := range lines {\n\t\ttext := lines[i]\n\t\tif i == 0 {\n\t\t\tout += result + indicator + text + newLine\n\t\t\tcontinue\n\t\t}\n\t\tif last {\n\t\t\tindicator = emptySpace\n\t\t} else {\n\t\t\tindicator = continueItem\n\t\t}\n\t\tout += result + indicator + text + newLine\n\t}\n\n\treturn out\n}\n\nfunc (p *printer) printItems(t []ObjMetadataTree, spaces []bool) string {\n\tvar result string\n\tfor i, f := range t {\n\t\tlast := i == len(t)-1\n\t\tresult += p.printText(f.Text(), spaces, last)\n\t\tif len(f.Items()) > 0 {\n\t\t\tspacesChild := append(spaces, last)\n\t\t\tresult += p.printItems(f.Items(), spacesChild)\n\t\t}\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "internal/utils/apply.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/kustomize/api/konfig\"\n\n\t\"github.com/fluxcd/cli-utils/pkg/kstatus/polling\"\n\trunclient \"github.com/fluxcd/pkg/runtime/client\"\n\t\"github.com/fluxcd/pkg/ssa\"\n\t\"github.com/fluxcd/pkg/ssa/normalize\"\n\tssautil \"github.com/fluxcd/pkg/ssa/utils\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/kustomization\"\n)\n\n// Apply is the equivalent of 'kubectl apply --server-side -f'.\n// If the given manifest is a kustomization.yaml, then apply performs the equivalent of 'kubectl apply --server-side -k'.\nfunc Apply(ctx context.Context, rcg genericclioptions.RESTClientGetter, opts *runclient.Options, root, manifestPath string) (string, error) {\n\tobjs, err := readObjects(root, manifestPath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif len(objs) == 0 {\n\t\treturn \"\", fmt.Errorf(\"no Kubernetes objects found at: %s\", manifestPath)\n\t}\n\n\tif err := normalize.UnstructuredList(objs); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tchangeSet := ssa.NewChangeSet()\n\n\t// contains only CRDs and Namespaces\n\tvar stageOne []*unstructured.Unstructured\n\n\t// contains all objects except for CRDs and Namespaces\n\tvar stageTwo []*unstructured.Unstructured\n\n\tfor _, u := range objs {\n\t\tif ssautil.IsClusterDefinition(u) {\n\t\t\tstageOne = append(stageOne, u)\n\t\t} else {\n\t\t\tstageTwo = append(stageTwo, u)\n\t\t}\n\t}\n\n\tif len(stageOne) > 0 {\n\t\tcs, err := applySet(ctx, rcg, opts, stageOne)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tchangeSet.Append(cs.Entries)\n\t}\n\n\tif len(changeSet.Entries) > 0 {\n\t\tif err := waitForSet(rcg, opts, changeSet); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\n\tif len(stageTwo) > 0 {\n\t\tcs, err := applySet(ctx, rcg, opts, stageTwo)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tchangeSet.Append(cs.Entries)\n\t}\n\n\treturn changeSet.String(), nil\n}\n\nfunc readObjects(root, manifestPath string) ([]*unstructured.Unstructured, error) {\n\tfi, err := os.Lstat(manifestPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif fi.IsDir() || !fi.Mode().IsRegular() {\n\t\treturn nil, fmt.Errorf(\"expected %q to be a file\", manifestPath)\n\t}\n\n\tif isRecognizedKustomizationFile(manifestPath) {\n\t\tresources, err := kustomization.BuildWithRoot(root, filepath.Dir(manifestPath))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn ssautil.ReadObjects(bytes.NewReader(resources))\n\t}\n\n\tms, err := os.Open(manifestPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer ms.Close()\n\n\treturn ssautil.ReadObjects(bufio.NewReader(ms))\n}\n\nfunc newManager(rcg genericclioptions.RESTClientGetter, opts *runclient.Options) (*ssa.ResourceManager, error) {\n\tcfg, err := KubeConfig(rcg, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trestMapper, err := rcg.ToRESTMapper()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkubeClient, err := client.New(cfg, client.Options{Mapper: restMapper, Scheme: NewScheme()})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkubePoller := polling.NewStatusPoller(kubeClient, restMapper, polling.Options{})\n\n\treturn ssa.NewResourceManager(kubeClient, kubePoller, ssa.Owner{\n\t\tField: \"flux\",\n\t\tGroup: \"fluxcd.io\",\n\t}), nil\n\n}\n\nfunc applySet(ctx context.Context, rcg genericclioptions.RESTClientGetter, opts *runclient.Options, objects []*unstructured.Unstructured) (*ssa.ChangeSet, error) {\n\tman, err := newManager(rcg, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn man.ApplyAll(ctx, objects, ssa.DefaultApplyOptions())\n}\n\nfunc waitForSet(rcg genericclioptions.RESTClientGetter, opts *runclient.Options, changeSet *ssa.ChangeSet) error {\n\tman, err := newManager(rcg, opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn man.WaitForSet(changeSet.ToObjMetadataSet(), ssa.WaitOptions{Interval: 2 * time.Second, Timeout: time.Minute})\n}\n\nfunc isRecognizedKustomizationFile(path string) bool {\n\tbase := filepath.Base(path)\n\tfor _, v := range konfig.RecognizedKustomizationFileNames() {\n\t\tif base == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "internal/utils/hex.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n)\n\n// hexRegexp matches any hexadecimal notation between 40 and 128 characters.\nvar hexRegexp = regexp.MustCompile(`\\b[a-f0-9]{40,128}\\b`)\n\n// TruncateHex will replace any hexadecimal notation between 40 and 128\n// characters (SHA-1 up to SHA-512) within the given string with a truncated\n// version of 8 characters.\nfunc TruncateHex(str string) string {\n\tif str == \"\" {\n\t\treturn \"\"\n\t}\n\thits := hexRegexp.FindAllString(str, -1)\n\tfor _, v := range hits {\n\t\tstr = strings.Replace(str, v, string([]rune(v)[:8]), -1)\n\t}\n\treturn str\n}\n"
  },
  {
    "path": "internal/utils/hex_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestTruncateHex(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tstr  string\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"SHA1 hash\",\n\t\t\tstr:  \"16cfcc0b9066b3234dda29927ac1c19860d9663f\",\n\t\t\twant: \"16cfcc0b\",\n\t\t},\n\t\t{\n\t\t\tname: \"SHA256 hash\",\n\t\t\tstr:  \"c2448c95e262b10f9c1137bf1472f51c04dffca76966ff15eff409d0b300c0b0\",\n\t\t\twant: \"c2448c95\",\n\t\t},\n\t\t{\n\t\t\tname: \"BLAKE3 hash\",\n\t\t\tstr:  \"d7b332559f4e57c01dcbe24e53346b4e47696fd2a07f39639a4017a8c3a1d045\",\n\t\t\twant: \"d7b33255\",\n\t\t},\n\t\t{\n\t\t\tname: \"SHA512 hash\",\n\t\t\tstr:  \"dd81564b7e1e1d5986b166c21963d602f47f8610bf2a6ebbfd2f9c1e5ef05ef134f07e587383cbc049325c43e0e6817b5a282a74c0d569a5e057118484989781\",\n\t\t\twant: \"dd81564b\",\n\t\t},\n\t\t{\n\t\t\tname: \"part of digest\",\n\t\t\tstr:  \"sha256:c2448c95e262b10f9c1137bf1472f51c04dffca76966ff15eff409d0b300c0b0\",\n\t\t\twant: \"sha256:c2448c95\",\n\t\t},\n\t\t{\n\t\t\tname: \"part of revision with digest\",\n\t\t\tstr:  \"tag@sha256:c2448c95e262b10f9c1137bf1472f51c04dffca76966ff15eff409d0b300c0b0\",\n\t\t\twant: \"tag@sha256:c2448c95\",\n\t\t},\n\t\t{\n\t\t\tname: \"legacy revision with hash\",\n\t\t\tstr:  \"HEAD/16cfcc0b9066b3234dda29927ac1c19860d9663f\",\n\t\t\twant: \"HEAD/16cfcc0b\",\n\t\t},\n\t\t{\n\t\t\tname: \"hex exceeding max length\",\n\t\t\tstr:  \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n\t\t\twant: \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n\t\t},\n\t\t{\n\t\t\tname: \"hex under min length\",\n\t\t\tstr:  \"ffffffffffffff\",\n\t\t\twant: \"ffffffffffffff\",\n\t\t},\n\t\t{\n\t\t\tname: \"within string\",\n\t\t\tstr:  \"this is a lengthy string with a hash 16cfcc0b9066b3234dda29927ac1c19860d9663f in it\",\n\t\t\twant: \"this is a lengthy string with a hash 16cfcc0b in it\",\n\t\t},\n\t\t{\n\t\t\tname: \"within string (quoted)\",\n\t\t\tstr:  \"this is a lengthy string with a hash \\\"c2448c95e262b10f9c1137bf1472f51c04dffca76966ff15eff409d0b300c0b0\\\" in it\",\n\t\t\twant: \"this is a lengthy string with a hash \\\"c2448c95\\\" in it\",\n\t\t},\n\t\t{\n\t\t\tname: \"within string (single quoted)\",\n\t\t\tstr:  \"this is a lengthy string with a hash 'sha256:c2448c95e262b10f9c1137bf1472f51c04dffca76966ff15eff409d0b300c0b0' in it\",\n\t\t\twant: \"this is a lengthy string with a hash 'sha256:c2448c95' in it\",\n\t\t},\n\t\t{\n\t\t\tname: \"arbitrary string\",\n\t\t\tstr:  \"which should not be modified\",\n\t\t\twant: \"which should not be modified\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tg := NewWithT(t)\n\n\t\t\tg.Expect(TruncateHex(tt.str)).To(Equal(tt.want))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/utils/testdata/components-with-crds.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: flux-system\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  creationTimestamp: null\n  name: alerts.notification.toolkit.fluxcd.io\nspec:\n  group: notification.toolkit.fluxcd.io\n  names:\n    kind: Alert\n    listKind: AlertList\n    plural: alerts\n    singular: alert\n  scope: Namespaced\n  versions:\n  - name: v1beta1\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  creationTimestamp: null\n  name: buckets.source.toolkit.fluxcd.io\nspec:\n  group: source.toolkit.fluxcd.io\n  names:\n    kind: Bucket\n    listKind: BucketList\n    plural: buckets\n    singular: bucket\n  scope: Namespaced\n  versions:\n  - name: v1beta1\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: kustomize-controller\n  namespace: flux-system\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: notification-controller\n  namespace: flux-system\n"
  },
  {
    "path": "internal/utils/testdata/components-without-crds.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: flux-system\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: kustomize-controller\n  namespace: flux-system\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: notification-controller\n  namespace: flux-system\n"
  },
  {
    "path": "internal/utils/utils.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tnetworkingv1 \"k8s.io/api/networking/v1\"\n\trbacv1 \"k8s.io/api/rbac/v1\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\tapiruntime \"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\tsigyaml \"k8s.io/apimachinery/pkg/util/yaml\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/yaml\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\timageautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\timagereflectv1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n\tnotificationv1b3 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\trunclient \"github.com/fluxcd/pkg/runtime/client\"\n\t\"github.com/fluxcd/pkg/version\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\tswapi \"github.com/fluxcd/source-watcher/api/v2/v1beta1\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n)\n\ntype ExecMode string\n\nconst (\n\tModeOS       ExecMode = \"os.stderr|stdout\"\n\tModeStderrOS ExecMode = \"os.stderr\"\n\tModeCapture  ExecMode = \"capture.stderr|stdout\"\n)\n\nfunc ExecKubectlCommand(ctx context.Context, mode ExecMode, kubeConfigPath string, kubeContext string, args ...string) (string, error) {\n\tvar stdoutBuf, stderrBuf bytes.Buffer\n\n\tif kubeConfigPath != \"\" && len(filepath.SplitList(kubeConfigPath)) == 1 {\n\t\targs = append(args, \"--kubeconfig=\"+kubeConfigPath)\n\t}\n\n\tif kubeContext != \"\" {\n\t\targs = append(args, \"--context=\"+kubeContext)\n\t}\n\n\tc := exec.CommandContext(ctx, \"kubectl\", args...)\n\n\tif mode == ModeStderrOS {\n\t\tc.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)\n\t}\n\tif mode == ModeOS {\n\t\tc.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)\n\t\tc.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)\n\t}\n\n\tif mode == ModeStderrOS || mode == ModeOS {\n\t\tif err := c.Run(); err != nil {\n\t\t\treturn \"\", err\n\t\t} else {\n\t\t\treturn \"\", nil\n\t\t}\n\t}\n\n\tif mode == ModeCapture {\n\t\tc.Stdout = &stdoutBuf\n\t\tc.Stderr = &stderrBuf\n\t\tif err := c.Run(); err != nil {\n\t\t\treturn stderrBuf.String(), err\n\t\t} else {\n\t\t\treturn stdoutBuf.String(), nil\n\t\t}\n\t}\n\n\treturn \"\", nil\n}\n\nfunc KubeConfig(rcg genericclioptions.RESTClientGetter, opts *runclient.Options) (*rest.Config, error) {\n\tcfg, err := rcg.ToRESTConfig()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"kubernetes configuration load failed: %w\", err)\n\t}\n\n\t// avoid throttling request when some Flux CRDs are not registered\n\tcfg.QPS = opts.QPS\n\tcfg.Burst = opts.Burst\n\n\treturn cfg, nil\n}\n\n// Create the Scheme, methods for serializing and deserializing API objects\n// which can be shared by tests.\nfunc NewScheme() *apiruntime.Scheme {\n\tscheme := apiruntime.NewScheme()\n\t_ = apiextensionsv1.AddToScheme(scheme)\n\t_ = corev1.AddToScheme(scheme)\n\t_ = rbacv1.AddToScheme(scheme)\n\t_ = appsv1.AddToScheme(scheme)\n\t_ = networkingv1.AddToScheme(scheme)\n\t_ = sourcev1.AddToScheme(scheme)\n\t_ = kustomizev1.AddToScheme(scheme)\n\t_ = helmv2.AddToScheme(scheme)\n\t_ = notificationv1.AddToScheme(scheme)\n\t_ = notificationv1b3.AddToScheme(scheme)\n\t_ = imagereflectv1.AddToScheme(scheme)\n\t_ = imageautov1.AddToScheme(scheme)\n\t_ = swapi.AddToScheme(scheme)\n\treturn scheme\n}\n\nfunc KubeClient(rcg genericclioptions.RESTClientGetter, opts *runclient.Options) (client.WithWatch, error) {\n\tcfg, err := rcg.ToRESTConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcfg.QPS = opts.QPS\n\tcfg.Burst = opts.Burst\n\n\tscheme := NewScheme()\n\tkubeClient, err := client.NewWithWatch(cfg, client.Options{\n\t\tScheme: scheme,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"kubernetes client initialization failed: %w\", err)\n\t}\n\n\treturn kubeClient, nil\n}\n\n// SplitKubeConfigPath splits the given KUBECONFIG path based on the runtime OS\n// target.\n//\n// Ref: https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable\nfunc SplitKubeConfigPath(path string) []string {\n\tvar sep string\n\tswitch runtime.GOOS {\n\tcase \"windows\":\n\t\tsep = \";\"\n\tdefault:\n\t\tsep = \":\"\n\t}\n\treturn strings.Split(path, sep)\n}\n\nfunc ContainsItemString(s []string, e string) bool {\n\tfor _, a := range s {\n\t\tif a == e {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc ContainsEqualFoldItemString(s []string, e string) (string, bool) {\n\tfor _, a := range s {\n\t\tif strings.EqualFold(a, e) {\n\t\t\treturn a, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\n// ParseNamespacedName extracts the NamespacedName of a resource\n// based on the '<namespace>/<name>' format\nfunc ParseNamespacedName(input string) types.NamespacedName {\n\tparts := strings.Split(input, \"/\")\n\tif len(parts) == 2 {\n\t\treturn types.NamespacedName{\n\t\t\tNamespace: parts[0],\n\t\t\tName:      parts[1],\n\t\t}\n\t}\n\treturn types.NamespacedName{\n\t\tName: input,\n\t}\n}\n\n// ParseObjectKindName extracts the kind and name of a resource\n// based on the '<kind>/<name>' format\nfunc ParseObjectKindName(input string) (kind, name string) {\n\tname = input\n\tparts := strings.Split(input, \"/\")\n\tif len(parts) == 2 {\n\t\tkind, name = parts[0], parts[1]\n\t}\n\treturn kind, name\n}\n\n// ParseObjectKindNameNamespace extracts the kind, name and namespace of a resource\n// based on the '<kind>/<name>.<namespace>' format\nfunc ParseObjectKindNameNamespace(input string) (kind, name, namespace string) {\n\tkind, name = ParseObjectKindName(input)\n\n\tif nn := strings.Split(name, \".\"); len(nn) > 1 {\n\t\tname = strings.Join(nn[:len(nn)-1], \".\")\n\t\tnamespace = nn[len(nn)-1]\n\t}\n\n\treturn kind, name, namespace\n}\n\nfunc MakeDependsOn(deps []string) []meta.NamespacedObjectReference {\n\trefs := []meta.NamespacedObjectReference{}\n\tfor _, dep := range deps {\n\t\tparts := strings.Split(dep, \"/\")\n\t\tdepNamespace := \"\"\n\t\tdepName := \"\"\n\t\tif len(parts) > 1 {\n\t\t\tdepNamespace = parts[0]\n\t\t\tdepName = parts[1]\n\t\t} else {\n\t\t\tdepName = parts[0]\n\t\t}\n\t\trefs = append(refs, meta.NamespacedObjectReference{\n\t\t\tNamespace: depNamespace,\n\t\t\tName:      depName,\n\t\t})\n\t}\n\treturn refs\n}\n\nfunc ValidateComponents(components []string) error {\n\tdefaults := install.MakeDefaultOptions()\n\tbootstrapAllComponents := append(defaults.Components, defaults.ComponentsExtra...)\n\tfor _, component := range components {\n\t\tif !ContainsItemString(bootstrapAllComponents, component) {\n\t\t\treturn fmt.Errorf(\"component %s is not available\", component)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// CompatibleVersion returns if the provided binary version is compatible\n// with the given target version. At present, this is true if the target\n// version is equal to the MINOR range of the binary, or if the binary\n// version is a prerelease.\nfunc CompatibleVersion(binary, target string) bool {\n\tbinSv, err := version.ParseVersion(binary)\n\tif err != nil {\n\t\treturn false\n\t}\n\t// Assume prerelease builds are compatible.\n\tif binSv.Prerelease() != \"\" {\n\t\treturn true\n\t}\n\ttargetSv, err := version.ParseVersion(target)\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn binSv.Major() == targetSv.Major() && binSv.Minor() == targetSv.Minor()\n}\n\nfunc ExtractCRDs(inManifestPath, outManifestPath string) error {\n\tmanifests, err := os.ReadFile(inManifestPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcrds := \"\"\n\treader := sigyaml.NewYAMLOrJSONDecoder(bytes.NewReader(manifests), 2048)\n\n\tfor {\n\t\tvar obj unstructured.Unstructured\n\t\terr := reader.Decode(&obj)\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif obj.GetKind() == \"CustomResourceDefinition\" {\n\t\t\tb, err := obj.MarshalJSON()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ty, err := yaml.JSONToYAML(b)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcrds += \"---\\n\" + string(y)\n\t\t}\n\t}\n\n\tif crds == \"\" {\n\t\treturn fmt.Errorf(\"no CRDs found in %s\", inManifestPath)\n\t}\n\n\treturn os.WriteFile(outManifestPath, []byte(crds), os.ModePerm)\n}\n"
  },
  {
    "path": "internal/utils/utils_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n)\n\nfunc TestCompatibleVersion(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tbinary string\n\t\ttarget string\n\t\twant   bool\n\t}{\n\t\t{\"different major version\", \"1.1.0\", \"0.1.0\", false},\n\t\t{\"different minor version\", \"0.1.0\", \"0.2.0\", false},\n\t\t{\"same version\", \"0.1.0\", \"0.1.0\", true},\n\t\t{\"binary patch version ahead\", \"0.1.1\", \"0.1.0\", true},\n\t\t{\"target patch version ahead\", \"0.1.1\", \"0.1.2\", true},\n\t\t{\"prerelease binary\", \"0.0.0-dev.0\", \"0.1.0\", true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := CompatibleVersion(tt.binary, tt.target); got != tt.want {\n\t\t\t\tt.Errorf(\"CompatibleVersion() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseObjectKindNameNamespace(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tinput         string\n\t\twantKind      string\n\t\twantName      string\n\t\twantNamespace string\n\t}{\n\t\t{\"with kind name namespace\", \"Kustomization/foo.flux-system\", \"Kustomization\", \"foo\", \"flux-system\"},\n\t\t{\"without namespace\", \"Kustomization/foo\", \"Kustomization\", \"foo\", \"\"},\n\t\t{\"name with dots\", \"Kustomization/foo.bar.flux-system\", \"Kustomization\", \"foo.bar\", \"flux-system\"},\n\t\t{\"multiple slashes\", \"foo/bar/baz\", \"\", \"foo/bar/baz\", \"\"},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgotKind, gotName, gotNamespace := ParseObjectKindNameNamespace(tt.input)\n\t\t\tif gotKind != tt.wantKind {\n\t\t\t\tt.Errorf(\"kind = %s, want %s\", gotKind, tt.wantKind)\n\t\t\t}\n\t\t\tif gotName != tt.wantName {\n\t\t\t\tt.Errorf(\"name = %s, want %s\", gotName, tt.wantName)\n\t\t\t}\n\t\t\tif gotNamespace != tt.wantNamespace {\n\t\t\t\tt.Errorf(\"namespace = %s, want %s\", gotNamespace, tt.wantNamespace)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMakeDependsOn(t *testing.T) {\n\tinput := []string{\n\t\t\"someNSA/someNameA\",\n\t\t\"someNSB/someNameB\",\n\t\t\"someNameC\",\n\t\t\"someNSD/\",\n\t\t\"\",\n\t}\n\twant := []meta.NamespacedObjectReference{\n\t\t{Namespace: \"someNSA\", Name: \"someNameA\"},\n\t\t{Namespace: \"someNSB\", Name: \"someNameB\"},\n\t\t{Namespace: \"\", Name: \"someNameC\"},\n\t\t{Namespace: \"someNSD\", Name: \"\"},\n\t\t{Namespace: \"\", Name: \"\"},\n\t}\n\n\tgot := MakeDependsOn(input)\n\tif !reflect.DeepEqual(got, want) {\n\t\tt.Errorf(\"MakeDependsOn() = %v, want %v\", got, want)\n\t}\n}\n\nfunc TestValidateComponents(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tinput     []string\n\t\texpectErr bool\n\t}{\n\t\t{\"default and extra components\", []string{\"source-controller\", \"image-reflector-controller\"}, false},\n\t\t{\"unknown components\", []string{\"some-comp-1\", \"some-comp-2\"}, true},\n\t\t{\"mix of default and unknown\", []string{\"source-controller\", \"some-comp-1\"}, true},\n\t\t{\"empty\", []string{}, false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif err := ValidateComponents(tt.input); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"ValidateComponents() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestExtractCRDs(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tinManifestFile string\n\t\texpectErr      bool\n\t}{\n\t\t{\"with crds\", \"components-with-crds.yaml\", false},\n\t\t{\"without crds\", \"components-without-crds.yaml\", true},\n\t\t{\"non-existent file\", \"non-existent-file.yaml\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\toutManifestPath := filepath.Join(t.TempDir(), \"crds.yaml\")\n\t\t\tinManifestPath := filepath.Join(\"testdata\", tt.inManifestFile)\n\t\t\tif err := ExtractCRDs(inManifestPath, outManifestPath); (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"ExtractCRDs() error = %v, expectErr %v\", err, tt.expectErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "manifests/bases/helm-controller/account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: helm-controller\n"
  },
  {
    "path": "manifests/bases/helm-controller/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- https://github.com/fluxcd/helm-controller/releases/download/v1.5.3/helm-controller.crds.yaml\n- https://github.com/fluxcd/helm-controller/releases/download/v1.5.3/helm-controller.deployment.yaml\n- account.yaml\ntransformers:\n- labels.yaml\npatches:\n- target:\n    group: apps\n    version: v1\n    kind: Deployment\n    name: helm-controller\n  path: patch.yaml\n"
  },
  {
    "path": "manifests/bases/helm-controller/labels.yaml",
    "content": "apiVersion: builtin\nkind: LabelTransformer\nmetadata:\n  name: labels\nlabels:\n  app.kubernetes.io/component: helm-controller\n  app.kubernetes.io/part-of: flux\nfieldSpecs:\n  - path: metadata/labels\n    create: true\n  - kind: Deployment\n    path: spec/template/metadata/labels\n    create: true\n"
  },
  {
    "path": "manifests/bases/helm-controller/patch.yaml",
    "content": "- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --events-addr=http://notification-controller.flux-system.svc.cluster.local./\n- op: add\n  path: /spec/template/spec/serviceAccountName\n  value: helm-controller\n- op: add\n  path: /spec/template/spec/priorityClassName\n  value: system-cluster-critical\n- op: add\n  path: /spec/template/spec/containers/0/env/-\n  value:\n    name: GOMEMLIMIT\n    valueFrom:\n      resourceFieldRef:\n        containerName: manager\n        resource: limits.memory\n"
  },
  {
    "path": "manifests/bases/image-automation-controller/account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: image-automation-controller\n"
  },
  {
    "path": "manifests/bases/image-automation-controller/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.1/image-automation-controller.crds.yaml\n- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.1/image-automation-controller.deployment.yaml\n- account.yaml\ntransformers:\n- labels.yaml\npatches:\n- target:\n    group: apps\n    version: v1\n    kind: Deployment\n    name: image-automation-controller\n  path: patch.yaml\n"
  },
  {
    "path": "manifests/bases/image-automation-controller/labels.yaml",
    "content": "apiVersion: builtin\nkind: LabelTransformer\nmetadata:\n  name: labels\nlabels:\n  app.kubernetes.io/component: image-automation-controller\n  app.kubernetes.io/part-of: flux\nfieldSpecs:\n  - path: metadata/labels\n    create: true\n  - kind: Deployment\n    path: spec/template/metadata/labels\n    create: true\n"
  },
  {
    "path": "manifests/bases/image-automation-controller/patch.yaml",
    "content": "- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --events-addr=http://notification-controller.flux-system.svc.cluster.local./\n- op: add\n  path: /spec/template/spec/serviceAccountName\n  value: image-automation-controller\n- op: add\n  path: /spec/template/spec/containers/0/env/-\n  value:\n    name: GOMEMLIMIT\n    valueFrom:\n      resourceFieldRef:\n        containerName: manager\n        resource: limits.memory\n"
  },
  {
    "path": "manifests/bases/image-reflector-controller/account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: image-reflector-controller\n"
  },
  {
    "path": "manifests/bases/image-reflector-controller/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.1/image-reflector-controller.crds.yaml\n- https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.1/image-reflector-controller.deployment.yaml\n- account.yaml\ntransformers:\n- labels.yaml\npatches:\n- target:\n    group: apps\n    version: v1\n    kind: Deployment\n    name: image-reflector-controller\n  path: patch.yaml\n"
  },
  {
    "path": "manifests/bases/image-reflector-controller/labels.yaml",
    "content": "apiVersion: builtin\nkind: LabelTransformer\nmetadata:\n  name: labels\nlabels:\n  app.kubernetes.io/component: image-reflector-controller\n  app.kubernetes.io/part-of: flux\nfieldSpecs:\n  - path: metadata/labels\n    create: true\n  - kind: Deployment\n    path: spec/template/metadata/labels\n    create: true\n"
  },
  {
    "path": "manifests/bases/image-reflector-controller/patch.yaml",
    "content": "- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --events-addr=http://notification-controller.flux-system.svc.cluster.local./\n- op: add\n  path: /spec/template/spec/serviceAccountName\n  value: image-reflector-controller\n- op: add\n  path: /spec/template/spec/containers/0/env/-\n  value:\n    name: GOMEMLIMIT\n    valueFrom:\n      resourceFieldRef:\n        containerName: manager\n        resource: limits.memory\n"
  },
  {
    "path": "manifests/bases/kustomize-controller/account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: kustomize-controller\n"
  },
  {
    "path": "manifests/bases/kustomize-controller/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.2/kustomize-controller.crds.yaml\n- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.2/kustomize-controller.deployment.yaml\n- account.yaml\ntransformers:\n- labels.yaml\npatches:\n- target:\n    group: apps\n    version: v1\n    kind: Deployment\n    name: kustomize-controller\n  path: patch.yaml\n"
  },
  {
    "path": "manifests/bases/kustomize-controller/labels.yaml",
    "content": "apiVersion: builtin\nkind: LabelTransformer\nmetadata:\n  name: labels\nlabels:\n  app.kubernetes.io/component: kustomize-controller\n  app.kubernetes.io/part-of: flux\nfieldSpecs:\n  - path: metadata/labels\n    create: true\n  - kind: Deployment\n    path: spec/template/metadata/labels\n    create: true\n"
  },
  {
    "path": "manifests/bases/kustomize-controller/patch.yaml",
    "content": "- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --events-addr=http://notification-controller.flux-system.svc.cluster.local./\n- op: add\n  path: /spec/template/spec/serviceAccountName\n  value: kustomize-controller\n- op: add\n  path: /spec/template/spec/priorityClassName\n  value: system-cluster-critical\n- op: add\n  path: /spec/template/spec/containers/0/env/-\n  value:\n    name: GOMEMLIMIT\n    valueFrom:\n      resourceFieldRef:\n        containerName: manager\n        resource: limits.memory\n"
  },
  {
    "path": "manifests/bases/notification-controller/account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: notification-controller\n"
  },
  {
    "path": "manifests/bases/notification-controller/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- https://github.com/fluxcd/notification-controller/releases/download/v1.8.2/notification-controller.crds.yaml\n- https://github.com/fluxcd/notification-controller/releases/download/v1.8.2/notification-controller.deployment.yaml\n- account.yaml\ntransformers:\n- labels.yaml\npatches:\n  - target:\n      group: apps\n      version: v1\n      kind: Deployment\n      name: notification-controller\n    path: patch.yaml\n"
  },
  {
    "path": "manifests/bases/notification-controller/labels.yaml",
    "content": "apiVersion: builtin\nkind: LabelTransformer\nmetadata:\n  name: labels\nlabels:\n  app.kubernetes.io/component: notification-controller\n  app.kubernetes.io/part-of: flux\nfieldSpecs:\n  - path: metadata/labels\n    create: true\n  - kind: Deployment\n    path: spec/template/metadata/labels\n    create: true\n"
  },
  {
    "path": "manifests/bases/notification-controller/patch.yaml",
    "content": "- op: add\n  path: /spec/template/spec/serviceAccountName\n  value: notification-controller\n- op: add\n  path: /spec/template/spec/containers/0/env/-\n  value:\n    name: GOMEMLIMIT\n    valueFrom:\n      resourceFieldRef:\n        containerName: manager\n        resource: limits.memory\n"
  },
  {
    "path": "manifests/bases/source-controller/account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: source-controller\n"
  },
  {
    "path": "manifests/bases/source-controller/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- https://github.com/fluxcd/source-controller/releases/download/v1.8.1/source-controller.crds.yaml\n- https://github.com/fluxcd/source-controller/releases/download/v1.8.1/source-controller.deployment.yaml\n- account.yaml\ntransformers:\n- labels.yaml\npatches:\n- target:\n    group: apps\n    version: v1\n    kind: Deployment\n    name: source-controller\n  path: patch.yaml\n"
  },
  {
    "path": "manifests/bases/source-controller/labels.yaml",
    "content": "apiVersion: builtin\nkind: LabelTransformer\nmetadata:\n  name: labels\nlabels:\n  app.kubernetes.io/component: source-controller\n  app.kubernetes.io/part-of: flux\nfieldSpecs:\n  - path: metadata/labels\n    create: true\n  - kind: Deployment\n    path: spec/template/metadata/labels\n    create: true\n"
  },
  {
    "path": "manifests/bases/source-controller/patch.yaml",
    "content": "- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --events-addr=http://notification-controller.flux-system.svc.cluster.local./\n- op: add\n  path: /spec/template/spec/serviceAccountName\n  value: source-controller\n- op: add\n  path: /spec/template/spec/priorityClassName\n  value: system-cluster-critical\n- op: add\n  path: /spec/template/spec/containers/0/env/-\n  value:\n    name: GOMEMLIMIT\n    valueFrom:\n      resourceFieldRef:\n        containerName: manager\n        resource: limits.memory\n"
  },
  {
    "path": "manifests/bases/source-watcher/account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: source-watcher\n"
  },
  {
    "path": "manifests/bases/source-watcher/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- https://github.com/fluxcd/source-watcher/releases/download/v2.1.1/source-watcher.crds.yaml\n- https://github.com/fluxcd/source-watcher/releases/download/v2.1.1/source-watcher.deployment.yaml\n- account.yaml\ntransformers:\n- labels.yaml\npatches:\n- target:\n    group: apps\n    version: v1\n    kind: Deployment\n    name: source-watcher\n  path: patch.yaml\n"
  },
  {
    "path": "manifests/bases/source-watcher/labels.yaml",
    "content": "apiVersion: builtin\nkind: LabelTransformer\nmetadata:\n  name: labels\nlabels:\n  app.kubernetes.io/component: source-watcher\n  app.kubernetes.io/part-of: flux\nfieldSpecs:\n  - path: metadata/labels\n    create: true\n  - kind: Deployment\n    path: spec/template/metadata/labels\n    create: true\n"
  },
  {
    "path": "manifests/bases/source-watcher/patch.yaml",
    "content": "- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --events-addr=http://notification-controller.flux-system.svc.cluster.local./\n- op: add\n  path: /spec/template/spec/serviceAccountName\n  value: source-watcher\n- op: add\n  path: /spec/template/spec/priorityClassName\n  value: system-cluster-critical\n- op: add\n  path: /spec/template/spec/containers/0/env/-\n  value:\n    name: GOMEMLIMIT\n    valueFrom:\n      resourceFieldRef:\n        containerName: manager\n        resource: limits.memory\n"
  },
  {
    "path": "manifests/crds/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- https://github.com/fluxcd/source-controller/releases/download/v1.8.1/source-controller.crds.yaml\n- https://github.com/fluxcd/kustomize-controller/releases/download/v1.8.2/kustomize-controller.crds.yaml\n- https://github.com/fluxcd/helm-controller/releases/download/v1.5.3/helm-controller.crds.yaml\n- https://github.com/fluxcd/notification-controller/releases/download/v1.8.2/notification-controller.crds.yaml\n- https://github.com/fluxcd/image-reflector-controller/releases/download/v1.1.1/image-reflector-controller.crds.yaml\n- https://github.com/fluxcd/image-automation-controller/releases/download/v1.1.1/image-automation-controller.crds.yaml\n- https://github.com/fluxcd/source-watcher/releases/download/v2.1.1/source-watcher.crds.yaml\n"
  },
  {
    "path": "manifests/install/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nnamespace: flux-system\nresources:\n  - namespace.yaml\n  - ../bases/source-controller\n  - ../bases/kustomize-controller\n  - ../bases/notification-controller\n  - ../bases/helm-controller\n  - ../bases/image-reflector-controller\n  - ../bases/image-automation-controller\n  - ../rbac\n  - ../policies\ntransformers:\n  - labels.yaml\nimages:\n  - name: fluxcd/source-controller\n    newName: ghcr.io/fluxcd/source-controller\n  - name: fluxcd/kustomize-controller\n    newName: ghcr.io/fluxcd/kustomize-controller\n  - name: fluxcd/helm-controller\n    newName: ghcr.io/fluxcd/helm-controller\n  - name: fluxcd/notification-controller\n    newName: ghcr.io/fluxcd/notification-controller\n  - name: fluxcd/image-reflector-controller\n    newName: ghcr.io/fluxcd/image-reflector-controller\n  - name: fluxcd/image-automation-controller\n    newName: ghcr.io/fluxcd/image-automation-controller\n"
  },
  {
    "path": "manifests/install/labels.yaml",
    "content": "apiVersion: builtin\nkind: LabelTransformer\nmetadata:\n  name: labels\nlabels:\n  app.kubernetes.io/part-of: flux\n  app.kubernetes.io/instance: flux-system\nfieldSpecs:\n  - path: metadata/labels\n    create: true\n  - kind: Deployment\n    path: spec/template/metadata/labels\n    create: true\n"
  },
  {
    "path": "manifests/install/namespace.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: flux-system\n  labels:\n    pod-security.kubernetes.io/warn: restricted\n    pod-security.kubernetes.io/warn-version: latest\n"
  },
  {
    "path": "manifests/monitoring/README.md",
    "content": "# :warning: Removal Notice\n\nStarting Flux v2.1.0, released August 24, 2023, the Flux monitoring\nconfigurations in this repository were marked as deprecated. The new monitoring\ndocs are available at [Flux monitoring](https://fluxcd.io/flux/monitoring/)\ndocs with new example configurations in\n[fluxcd/flux2-monitoring-example](https://github.com/fluxcd/flux2-monitoring-example/).\n\nThe deprecated configurations were removed in Flux v2.2 on December 13, 2023. All\nusers of these configurations are advised to use the new monitoring setup,\nfollowing the [docs](https://fluxcd.io/flux/monitoring/) and the\n[examples](https://github.com/fluxcd/flux2-monitoring-example/).\n\nAfter collecting a lot of user feedback about our monitoring recommendation, in\norder to serve most of the needs of the users, we decided to create a new\nmonitoring setup leveraging more of the kube-prometheus-stack, specifically\nkube-state-metrics, to enable configuring Flux custom metrics, see the [Flux\ncustom Prometheus metrics](https://fluxcd.io/flux/monitoring/custom-metrics/)\ndocs to learn more about it. Please refer to\n[fluxcd/flux2/4128](https://github.com/fluxcd/flux2/issues/4128) for a detailed\nexplanation about this change and the new capabilities offered by the new\nmonitoring setup.\n"
  },
  {
    "path": "manifests/openshift/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nnamespace: flux-system\nresources:\n  - namespace.yaml\n  - scc.yaml\n  - ../bases/source-controller\n  - ../bases/source-watcher\n  - ../bases/kustomize-controller\n  - ../bases/notification-controller\n  - ../bases/helm-controller\n  - ../bases/image-reflector-controller\n  - ../bases/image-automation-controller\n  - ../rbac\n  - ../policies\ntransformers:\n  - labels.yaml\nimages:\n  - name: fluxcd/source-controller\n    newName: ghcr.io/fluxcd/source-controller\n  - name: fluxcd/source-watcher\n    newName: ghcr.io/fluxcd/source-watcher\n  - name: fluxcd/kustomize-controller\n    newName: ghcr.io/fluxcd/kustomize-controller\n  - name: fluxcd/helm-controller\n    newName: ghcr.io/fluxcd/helm-controller\n  - name: fluxcd/notification-controller\n    newName: ghcr.io/fluxcd/notification-controller\n  - name: fluxcd/image-reflector-controller\n    newName: ghcr.io/fluxcd/image-reflector-controller\n  - name: fluxcd/image-automation-controller\n    newName: ghcr.io/fluxcd/image-automation-controller\npatches:\n  - patch: |\n      apiVersion: apps/v1\n      kind: Deployment\n      metadata:\n        name: all\n      spec:\n        template:\n          spec:\n            securityContext:\n              $patch: delete\n            containers:\n              - name: manager\n                securityContext:\n                  runAsUser: 65534\n                  seccompProfile:\n                    $patch: delete\n    target:\n      kind: Deployment\n"
  },
  {
    "path": "manifests/openshift/labels.yaml",
    "content": "apiVersion: builtin\nkind: LabelTransformer\nmetadata:\n  name: labels\nlabels:\n  app.kubernetes.io/part-of: flux\n  app.kubernetes.io/instance: flux-system\nfieldSpecs:\n  - path: metadata/labels\n    create: true\n  - kind: Deployment\n    path: spec/template/metadata/labels\n    create: true\n"
  },
  {
    "path": "manifests/openshift/namespace.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: flux-system\n"
  },
  {
    "path": "manifests/openshift/scc.yaml",
    "content": "# Allow Flux controllers to run as non-root on OpenShift\n# Docs: https://fluxcd.io/flux/installation/configuration/openshift/\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: flux-scc\nrules:\n  - apiGroups:\n      - security.openshift.io\n    resources:\n      - securitycontextconstraints\n    resourceNames:\n      - nonroot\n    verbs:\n      - use\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: flux-scc\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: flux-scc\nsubjects:\n  - kind: ServiceAccount\n    name: source-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: source-watcher\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: kustomize-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: helm-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: notification-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: image-reflector-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: image-automation-controller\n    namespace: flux-system\n"
  },
  {
    "path": "manifests/policies/allow-egress.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: allow-egress\nspec:\n  policyTypes:\n    - Ingress\n    - Egress\n  ingress:\n  - from:\n    - podSelector: {}\n  egress:\n    - {}\n  podSelector: {}\n"
  },
  {
    "path": "manifests/policies/allow-scraping.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: allow-scraping\nspec:\n  policyTypes:\n    - Ingress\n  ingress:\n    - from:\n        - namespaceSelector: {}\n      ports:\n        - protocol: TCP\n          port: 8080\n  podSelector: {}\n"
  },
  {
    "path": "manifests/policies/allow-webhooks.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: allow-webhooks\nspec:\n  policyTypes:\n    - Ingress\n  ingress:\n    - from:\n        - namespaceSelector: {}\n  podSelector:\n    matchLabels:\n      app: notification-controller\n"
  },
  {
    "path": "manifests/policies/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n  - allow-egress.yaml\n  - allow-scraping.yaml\n  - allow-webhooks.yaml\n"
  },
  {
    "path": "manifests/rbac/controller.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: crd-controller\nrules:\n- apiGroups: ['source.toolkit.fluxcd.io']\n  resources: ['*']\n  verbs: ['*']\n- apiGroups: ['kustomize.toolkit.fluxcd.io']\n  resources: ['*']\n  verbs: ['*']\n- apiGroups: ['helm.toolkit.fluxcd.io']\n  resources: ['*']\n  verbs: ['*']\n- apiGroups: ['notification.toolkit.fluxcd.io']\n  resources: ['*']\n  verbs: ['*']\n- apiGroups: ['image.toolkit.fluxcd.io']\n  resources: ['*']\n  verbs: ['*']\n- apiGroups: ['source.extensions.fluxcd.io']\n  resources: ['*']\n  verbs: ['*']\n- apiGroups:\n  - \"\"\n  resources:\n  - namespaces\n  - secrets\n  - configmaps\n  - serviceaccounts\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n# required by leader election\n- apiGroups:\n    - \"\"\n  resources:\n    - configmaps\n  verbs:\n    - get\n    - list\n    - watch\n    - create\n    - update\n    - patch\n    - delete\n- apiGroups:\n    - \"\"\n  resources:\n    - configmaps/status\n  verbs:\n    - get\n    - update\n    - patch\n- apiGroups:\n  - \"coordination.k8s.io\"\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n# required for object-level workload identity\n- apiGroups:\n  - \"\"\n  resources:\n  - serviceaccounts/token\n  verbs:\n  - create\n# required for flow control\n- nonResourceURLs:\n  - /livez/ping\n  verbs:\n  - head\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: crd-controller\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: crd-controller\nsubjects:\n  - kind: ServiceAccount\n    name: kustomize-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: helm-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: source-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: notification-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: image-reflector-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: image-automation-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: source-watcher\n    namespace: flux-system\n"
  },
  {
    "path": "manifests/rbac/edit.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: flux-edit\n  labels:\n    rbac.authorization.k8s.io/aggregate-to-edit: \"true\"\n    rbac.authorization.k8s.io/aggregate-to-admin: \"true\"\nrules:\n  - apiGroups:\n      - notification.toolkit.fluxcd.io\n      - source.toolkit.fluxcd.io\n      - source.extensions.fluxcd.io\n      - helm.toolkit.fluxcd.io\n      - image.toolkit.fluxcd.io\n      - kustomize.toolkit.fluxcd.io\n    resources: [\"*\"]\n    verbs:\n      - create\n      - delete\n      - deletecollection\n      - patch\n      - update\n"
  },
  {
    "path": "manifests/rbac/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n  - controller.yaml\n  - reconciler.yaml\n  - edit.yaml\n  - view.yaml\n  - resourcequota.yaml\n"
  },
  {
    "path": "manifests/rbac/reconciler.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: cluster-reconciler\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n  - kind: ServiceAccount\n    name: kustomize-controller\n    namespace: flux-system\n  - kind: ServiceAccount\n    name: helm-controller\n    namespace: flux-system\n"
  },
  {
    "path": "manifests/rbac/resourcequota.yaml",
    "content": "apiVersion: v1\nkind: ResourceQuota\nmetadata:\n  name: critical-pods\nspec:\n  hard:\n    pods: \"1000\"\n  scopeSelector:\n    matchExpressions:\n      - operator: In\n        scopeName: PriorityClass\n        values:\n          - system-node-critical\n          - system-cluster-critical\n"
  },
  {
    "path": "manifests/rbac/view.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: flux-view\n  labels:\n    rbac.authorization.k8s.io/aggregate-to-admin: \"true\"\n    rbac.authorization.k8s.io/aggregate-to-edit: \"true\"\n    rbac.authorization.k8s.io/aggregate-to-view: \"true\"\nrules:\n  - apiGroups:\n      - notification.toolkit.fluxcd.io\n      - source.toolkit.fluxcd.io\n      - source.extensions.fluxcd.io\n      - helm.toolkit.fluxcd.io\n      - image.toolkit.fluxcd.io\n      - kustomize.toolkit.fluxcd.io\n    resources: [\"*\"]\n    verbs:\n      - get\n      - list\n      - watch\n"
  },
  {
    "path": "manifests/scripts/bundle.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2020, 2021 The Flux authors. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -e\n\nIN_PATH=${1:-\"$(realpath $(dirname \"${BASH_SOURCE[0]}\")/../..)/manifests\"}\nOUT_PATH=${2:-\"$(realpath $(dirname \"${BASH_SOURCE[0]}\")/../..)/cmd/flux/manifests\"}\nTAR=${3}\n\ninfo() {\n    echo '[INFO] ' \"$@\"\n}\n\nfatal() {\n    echo '[ERROR] ' \"$@\" >&2\n    exit 1\n}\n\nbuild() {\n  info \"building $(basename $2)\"\n  kustomize build \"$1\" > \"$2\"\n}\n\nif ! [ -x \"$(command -v kustomize)\" ]; then\n  fatal 'kustomize is not installed'\nfi\n\nrm -rf $OUT_PATH\nmkdir -p $OUT_PATH\nfiles=\"\"\n\ninfo using kustomize \"$(kustomize version)\"\n\n# build controllers\nfor controller in ${IN_PATH}/bases/*/; do\n    output_path=\"${OUT_PATH}/$(basename $controller).yaml\"\n    build $controller $output_path\n    files+=\" $(basename $output_path)\"\ndone\n\n# build rbac\nrbac_path=\"${IN_PATH}/rbac\"\nrbac_output_path=\"${OUT_PATH}/rbac.yaml\"\nbuild $rbac_path $rbac_output_path\nfiles+=\" $(basename $rbac_output_path)\"\n\n# build policies\npolicies_path=\"${IN_PATH}/policies\"\npolicies_output_path=\"${OUT_PATH}/policies.yaml\"\nbuild $policies_path $policies_output_path\nfiles+=\" $(basename $policies_output_path)\"\n\n# create tarball\nif [[ -n $TAR ]];then\n  info \"archiving $TAR\"\n  cd ${OUT_PATH} && tar -czf $TAR $files\nfi\n"
  },
  {
    "path": "manifests/test/kustomization.yaml",
    "content": "# This overlay is used in end-to-end tests and contains all optional controllers.\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nnamespace: flux-system\nresources:\n  - namespace.yaml\n  - ../bases/source-controller\n  - ../bases/source-watcher\n  - ../bases/kustomize-controller\n  - ../bases/notification-controller\n  - ../bases/helm-controller\n  - ../bases/image-reflector-controller\n  - ../bases/image-automation-controller\n  - ../rbac\n  - ../policies\ntransformers:\n  - labels.yaml\nimages:\n  - name: fluxcd/source-controller\n    newName: ghcr.io/fluxcd/source-controller\n  - name: fluxcd/source-watcher\n    newName: ghcr.io/fluxcd/source-watcher\n  - name: fluxcd/kustomize-controller\n    newName: ghcr.io/fluxcd/kustomize-controller\n  - name: fluxcd/helm-controller\n    newName: ghcr.io/fluxcd/helm-controller\n  - name: fluxcd/notification-controller\n    newName: ghcr.io/fluxcd/notification-controller\n  - name: fluxcd/image-reflector-controller\n    newName: ghcr.io/fluxcd/image-reflector-controller\n  - name: fluxcd/image-automation-controller\n    newName: ghcr.io/fluxcd/image-automation-controller\npatches:\n  - target:\n      kind: Deployment\n      name: \"(kustomize-controller|helm-controller)\"\n    patch: |-\n      - op: add\n        path: /spec/template/spec/containers/0/args/-\n        value: --feature-gates=ExternalArtifact=true\n"
  },
  {
    "path": "manifests/test/labels.yaml",
    "content": "apiVersion: builtin\nkind: LabelTransformer\nmetadata:\n  name: labels\nlabels:\n  app.kubernetes.io/part-of: flux\n  app.kubernetes.io/instance: flux-system\nfieldSpecs:\n  - path: metadata/labels\n    create: true\n  - kind: Deployment\n    path: spec/template/metadata/labels\n    create: true\n"
  },
  {
    "path": "manifests/test/namespace.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: flux-system\n  labels:\n    pod-security.kubernetes.io/warn: restricted\n    pod-security.kubernetes.io/warn-version: latest\n"
  },
  {
    "path": "netlify.toml",
    "content": "[build]\n  command = \"mkdir -p site && cp docs/_redirects site/\"\n  publish = \"site\"\n"
  },
  {
    "path": "pkg/bootstrap/bootstrap.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bootstrap\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierr \"k8s.io/apimachinery/pkg/api/errors\"\n\tapimeta \"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\tapierrors \"k8s.io/apimachinery/pkg/util/errors\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\tsourcev1b2 \"github.com/fluxcd/source-controller/api/v1beta2\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync\"\n)\n\nvar (\n\tErrReconciledWithWarning = errors.New(\"reconciled with warning\")\n)\n\n// Reconciler reconciles and reports the health of different\n// components and kubernetes resources involved in the installation of Flux.\n//\n// It is recommended use the `ReconcilerWithSyncCheck` interface that also\n// reports the health of the GitRepository.\ntype Reconciler interface {\n\t// ReconcileComponents reconciles the components by generating the\n\t// manifests with the provided values, committing them to Git and\n\t// pushing to remote if there are any changes, and applying them\n\t// to the cluster.\n\tReconcileComponents(ctx context.Context, manifestsBase string, options install.Options, secretOpts sourcesecret.Options) error\n\n\t// ReconcileSourceSecret reconciles the source secret by generating\n\t// a new secret with the provided values if the secret does not\n\t// already exists on the cluster, or if any of the configuration\n\t// options changed.\n\tReconcileSourceSecret(ctx context.Context, options sourcesecret.Options) error\n\n\t// ReconcileSyncConfig reconciles the sync configuration by generating\n\t// the sync manifests with the provided values, committing them to Git\n\t// and pushing to remote if there are any changes.\n\tReconcileSyncConfig(ctx context.Context, options sync.Options) error\n\n\t// ReportKustomizationHealth reports about the health of the\n\t// Kustomization synchronizing the components.\n\tReportKustomizationHealth(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error\n\n\t// ReportComponentsHealth reports about the health for the components\n\t// and extra components in install.Options.\n\tReportComponentsHealth(ctx context.Context, options install.Options, timeout time.Duration) error\n}\n\ntype RepositoryReconciler interface {\n\t// ReconcileRepository reconciles an external Git repository.\n\tReconcileRepository(ctx context.Context) error\n}\n\n// ReconcilerWithSyncCheck extends the Reconciler interface to also report the health of the GitReposiotry\n// that syncs Flux on the cluster\ntype ReconcilerWithSyncCheck interface {\n\tReconciler\n\t// ReportGitRepoHealth reports about the health of the GitRepository synchronizing the components.\n\tReportGitRepoHealth(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error\n}\n\ntype PostGenerateSecretFunc func(ctx context.Context, secret corev1.Secret, options sourcesecret.Options) error\n\nfunc Run(ctx context.Context, reconciler Reconciler, manifestsBase string,\n\tinstallOpts install.Options, secretOpts sourcesecret.Options, syncOpts sync.Options,\n\tpollInterval, timeout time.Duration) error {\n\n\tvar err error\n\tif r, ok := reconciler.(RepositoryReconciler); ok {\n\t\tif err = r.ReconcileRepository(ctx); err != nil && !errors.Is(err, ErrReconciledWithWarning) {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := reconciler.ReconcileComponents(ctx, manifestsBase, installOpts, secretOpts); err != nil {\n\t\treturn err\n\t}\n\tif err := reconciler.ReconcileSourceSecret(ctx, secretOpts); err != nil {\n\t\treturn err\n\t}\n\tif err := reconciler.ReconcileSyncConfig(ctx, syncOpts); err != nil {\n\t\treturn err\n\t}\n\n\tvar errs []error\n\tif r, ok := reconciler.(ReconcilerWithSyncCheck); ok {\n\t\tif err := r.ReportGitRepoHealth(ctx, syncOpts, pollInterval, timeout); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\n\tif err := reconciler.ReportKustomizationHealth(ctx, syncOpts, pollInterval, timeout); err != nil {\n\t\terrs = append(errs, err)\n\t}\n\n\tif err := reconciler.ReportComponentsHealth(ctx, installOpts, timeout); err != nil {\n\t\terrs = append(errs, err)\n\t}\n\tif len(errs) > 0 {\n\t\terr = fmt.Errorf(\"bootstrap failed with %d health check failure(s): %w\", len(errs), apierrors.NewAggregate(errs))\n\t}\n\n\treturn err\n}\n\nfunc mustInstallManifests(ctx context.Context, kube client.Client, namespace string) bool {\n\tnamespacedName := types.NamespacedName{\n\t\tNamespace: namespace,\n\t\tName:      namespace,\n\t}\n\tvar k kustomizev1.Kustomization\n\tif err := kube.Get(ctx, namespacedName, &k); err != nil {\n\t\treturn true\n\t}\n\treturn k.Status.LastAppliedRevision == \"\"\n}\n\nfunc secretExists(ctx context.Context, kube client.Client, objKey client.ObjectKey) (bool, error) {\n\tif err := kube.Get(ctx, objKey, &corev1.Secret{}); err != nil {\n\t\tif apierr.IsNotFound(err) {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\nfunc reconcileSecret(ctx context.Context, kube client.Client, secret corev1.Secret) error {\n\tobjKey := client.ObjectKeyFromObject(&secret)\n\tvar existing corev1.Secret\n\terr := kube.Get(ctx, objKey, &existing)\n\tif err != nil {\n\t\tif apierr.IsNotFound(err) {\n\t\t\treturn kube.Create(ctx, &secret)\n\t\t}\n\t\treturn err\n\t}\n\texisting.StringData = secret.StringData\n\treturn kube.Update(ctx, &existing)\n}\n\nfunc reconcileImagePullSecret(ctx context.Context, kube client.Client, installOpts install.Options) error {\n\tcredentials := strings.SplitN(installOpts.RegistryCredential, \":\", 2)\n\tdcj, err := sourcesecret.GenerateDockerConfigJson(installOpts.Registry, credentials[0], credentials[1])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to generate docker config json: %w\", err)\n\t}\n\n\tsecret := corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace: installOpts.Namespace,\n\t\t\tName:      installOpts.ImagePullSecret,\n\t\t},\n\t\tStringData: map[string]string{\n\t\t\tcorev1.DockerConfigJsonKey: string(dcj),\n\t\t},\n\t\tType: corev1.SecretTypeDockerConfigJson,\n\t}\n\treturn reconcileSecret(ctx, kube, secret)\n}\n\nfunc kustomizationPathDiffers(ctx context.Context, kube client.Client, objKey client.ObjectKey, path string) (string, error) {\n\tvar k kustomizev1.Kustomization\n\tif err := kube.Get(ctx, objKey, &k); err != nil {\n\t\tif apierr.IsNotFound(err) {\n\t\t\treturn \"\", nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\tnormalizePath := func(p string) string {\n\t\t// remove the trailing '/' if the path is not './'\n\t\tif len(p) > 2 {\n\t\t\tp = strings.TrimSuffix(p, \"/\")\n\t\t}\n\t\treturn fmt.Sprintf(\"./%s\", strings.TrimPrefix(p, \"./\"))\n\t}\n\tif normalizePath(path) == normalizePath(k.Spec.Path) {\n\t\treturn \"\", nil\n\t}\n\treturn k.Spec.Path, nil\n}\n\ntype objectWithConditions interface {\n\tclient.Object\n\tGetConditions() []metav1.Condition\n}\n\nfunc objectReconciled(kube client.Client, objKey client.ObjectKey, clientObject objectWithConditions, expectRevision string) wait.ConditionWithContextFunc {\n\treturn func(ctx context.Context) (bool, error) {\n\t\t// for some reason, TypeMeta gets unset after kube.Get so we want to store the GVK and set it after\n\t\t// ref https://github.com/kubernetes-sigs/controller-runtime/issues/1517#issuecomment-844703142\n\t\tgvk := clientObject.GetObjectKind().GroupVersionKind()\n\t\tif err := kube.Get(ctx, objKey, clientObject); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tclientObject.GetObjectKind().SetGroupVersionKind(gvk)\n\n\t\tkind := gvk.Kind\n\t\tobj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(clientObject)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\t// Detect suspended object, as this would result in an endless wait\n\t\tif suspended, ok, _ := unstructured.NestedBool(obj, \"spec\", \"suspend\"); ok && suspended {\n\t\t\treturn false, fmt.Errorf(\"%s '%s' is suspended\", kind, objKey.String())\n\t\t}\n\n\t\t// Confirm the state we are observing is for the current generation\n\t\tif generation, ok, _ := unstructured.NestedInt64(obj, \"status\", \"observedGeneration\"); ok && generation != clientObject.GetGeneration() {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// Confirm the resource is healthy\n\t\tif c := apimeta.FindStatusCondition(clientObject.GetConditions(), meta.ReadyCondition); c != nil {\n\t\t\tswitch c.Status {\n\t\t\tcase metav1.ConditionTrue:\n\t\t\t\t// Confirm the given revision has been attempted by the controller\n\t\t\t\thasRev, err := hasRevision(kind, obj, expectRevision)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn false, err\n\t\t\t\t}\n\t\t\t\treturn hasRev, nil\n\t\t\tcase metav1.ConditionFalse:\n\t\t\t\treturn false, errors.New(c.Message)\n\t\t\t}\n\t\t}\n\t\treturn false, nil\n\t}\n}\n\n// hasRevision checks that the reconciled revision (for Kustomization this is `.status.lastAttemptedRevision`\n// and for Source APIs, it is stored in `.status.artifact.revision`) is the same as the expectedRev\nfunc hasRevision(kind string, obj map[string]interface{}, expectedRev string) (bool, error) {\n\tvar rev string\n\tswitch kind {\n\tcase sourcev1.GitRepositoryKind, sourcev1.OCIRepositoryKind, sourcev1.BucketKind, sourcev1.HelmChartKind:\n\t\trev, _, _ = unstructured.NestedString(obj, \"status\", \"artifact\", \"revision\")\n\tcase kustomizev1.KustomizationKind:\n\t\trev, _, _ = unstructured.NestedString(obj, \"status\", \"lastAttemptedRevision\")\n\tdefault:\n\t\treturn false, fmt.Errorf(\"cannot get status revision for kind: '%s'\", kind)\n\t}\n\treturn sourcev1b2.TransformLegacyRevision(rev) == expectedRev, nil\n}\n\nfunc retry(retries int, wait time.Duration, fn func() error) (err error) {\n\tfor i := 0; ; i++ {\n\t\terr = fn()\n\t\tif err == nil {\n\t\t\treturn\n\t\t}\n\t\tif i >= retries {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(wait)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/bootstrap/bootstrap_plain_git.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bootstrap\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/ProtonMail/go-crypto/openpgp\"\n\tgogit \"github.com/go-git/go-git/v5\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapimeta \"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/kustomize/api/konfig\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/cli-utils/pkg/object\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\t\"github.com/fluxcd/pkg/git\"\n\t\"github.com/fluxcd/pkg/git/repository\"\n\t\"github.com/fluxcd/pkg/kustomize/filesys\"\n\trunclient \"github.com/fluxcd/pkg/runtime/client\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n\t\"github.com/fluxcd/flux2/v2/pkg/log\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/install\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/kustomization\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync\"\n\t\"github.com/fluxcd/flux2/v2/pkg/status\"\n)\n\ntype PlainGitBootstrapper struct {\n\turl    string\n\tbranch string\n\n\tsignature             git.Signature\n\tcommitMessageAppendix string\n\n\tgpgKeyRing    openpgp.EntityList\n\tgpgPassphrase string\n\tgpgKeyID      string\n\n\trestClientGetter  genericclioptions.RESTClientGetter\n\trestClientOptions *runclient.Options\n\n\tpostGenerateSecret []PostGenerateSecretFunc\n\n\tgitClient repository.Client\n\tkube      client.Client\n\tlogger    log.Logger\n}\n\ntype GitOption interface {\n\tapplyGit(b *PlainGitBootstrapper)\n}\n\nfunc WithRepositoryURL(url string) GitOption {\n\treturn repositoryURLOption(url)\n}\n\ntype repositoryURLOption string\n\nfunc (o repositoryURLOption) applyGit(b *PlainGitBootstrapper) {\n\tb.url = string(o)\n}\n\nfunc WithPostGenerateSecretFunc(callback PostGenerateSecretFunc) GitOption {\n\treturn postGenerateSecret(callback)\n}\n\ntype postGenerateSecret PostGenerateSecretFunc\n\nfunc (o postGenerateSecret) applyGit(b *PlainGitBootstrapper) {\n\tb.postGenerateSecret = append(b.postGenerateSecret, PostGenerateSecretFunc(o))\n}\n\nfunc NewPlainGitProvider(git repository.Client, kube client.Client, opts ...GitOption) (*PlainGitBootstrapper, error) {\n\tb := &PlainGitBootstrapper{\n\t\tgitClient: git,\n\t\tkube:      kube,\n\t}\n\tfor _, opt := range opts {\n\t\topt.applyGit(b)\n\t}\n\treturn b, nil\n}\n\nfunc (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifestsBase string, options install.Options, _ sourcesecret.Options) error {\n\t// Clone if not already\n\tif _, err := b.gitClient.Head(); err != nil {\n\t\tif err != git.ErrNoGitRepository {\n\t\t\treturn err\n\t\t}\n\n\t\tb.logger.Actionf(\"cloning branch %q from Git repository %q\", b.branch, b.url)\n\t\tvar cloned bool\n\t\tif err = retry(1, 2*time.Second, func() (err error) {\n\t\t\tif err = b.cleanGitRepoDir(); err != nil {\n\t\t\t\tb.logger.Warningf(\" failed to clean directory for git repo: %w\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_, err = b.gitClient.Clone(ctx, b.url, repository.CloneConfig{\n\t\t\t\tCheckoutStrategy: repository.CheckoutStrategy{\n\t\t\t\t\tBranch: b.branch,\n\t\t\t\t},\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tb.logger.Warningf(\" clone failure: %s\", err)\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\tcloned = true\n\t\t\t}\n\t\t\treturn\n\t\t}); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to clone repository: %w\", err)\n\t\t}\n\t\tif cloned {\n\t\t\tb.logger.Successf(\"cloned repository\")\n\t\t}\n\t}\n\n\t// Generate component manifests\n\tb.logger.Actionf(\"generating component manifests\")\n\tmanifests, err := install.Generate(options, manifestsBase)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"component manifest generation failed: %w\", err)\n\t}\n\tb.logger.Successf(\"generated component manifests\")\n\n\t// Write generated files and make a commit\n\tvar signer *openpgp.Entity\n\tif b.gpgKeyRing != nil {\n\t\tsigner, err = getOpenPgpEntity(b.gpgKeyRing, b.gpgPassphrase, b.gpgKeyID)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to generate OpenPGP entity: %w\", err)\n\t\t}\n\t}\n\tcommitMsg := fmt.Sprintf(\"Add Flux %s component manifests\", options.Version)\n\tif b.commitMessageAppendix != \"\" {\n\t\tcommitMsg = commitMsg + \"\\n\\n\" + b.commitMessageAppendix\n\t}\n\n\tcommit, err := b.gitClient.Commit(git.Commit{\n\t\tAuthor:  b.signature,\n\t\tMessage: commitMsg,\n\t}, repository.WithFiles(map[string]io.Reader{\n\t\tmanifests.Path: strings.NewReader(manifests.Content),\n\t}), repository.WithSigner(signer))\n\tif err != nil && err != git.ErrNoStagedFiles {\n\t\treturn fmt.Errorf(\"failed to commit component manifests: %w\", err)\n\t}\n\n\tif err == nil {\n\t\tb.logger.Successf(\"committed component manifests to %q (%q)\", b.branch, commit)\n\t\tb.logger.Actionf(\"pushing component manifests to %q\", b.url)\n\t\tif err = b.gitClient.Push(ctx, repository.PushConfig{}); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to push manifests: %w\", err)\n\t\t}\n\t} else {\n\t\tb.logger.Successf(\"component manifests are up to date\")\n\t}\n\n\t// Conditionally install manifests\n\tif mustInstallManifests(ctx, b.kube, options.Namespace) {\n\t\tb.logger.Actionf(\"installing components in %q namespace\", options.Namespace)\n\n\t\tcomponentsYAML := filepath.Join(b.gitClient.Path(), manifests.Path)\n\t\tkfile := filepath.Join(filepath.Dir(componentsYAML), konfig.DefaultKustomizationFileName())\n\t\tif _, err := os.Stat(kfile); err == nil {\n\t\t\t// Apply the components and their patches\n\t\t\tif _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.gitClient.Path(), kfile); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\t// Apply the CRDs and controllers\n\t\t\tif _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.gitClient.Path(), componentsYAML); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tb.logger.Successf(\"installed components\")\n\t}\n\n\t// Reconcile image pull secret if needed\n\tif options.ImagePullSecret != \"\" && options.RegistryCredential != \"\" {\n\t\tif err := reconcileImagePullSecret(ctx, b.kube, options); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to reconcile image pull secret: %w\", err)\n\t\t}\n\t\tb.logger.Successf(\"reconciled image pull secret %s\", options.ImagePullSecret)\n\t}\n\n\tb.logger.Successf(\"reconciled components\")\n\treturn nil\n}\n\nfunc (b *PlainGitBootstrapper) ReconcileSourceSecret(ctx context.Context, options sourcesecret.Options) error {\n\t// Determine if there is an existing secret\n\tsecretKey := client.ObjectKey{Name: options.Name, Namespace: options.Namespace}\n\tb.logger.Actionf(\"determining if source secret %q exists\", secretKey)\n\tok, err := secretExists(ctx, b.kube, secretKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to determine if deploy key secret exists: %w\", err)\n\t}\n\n\t// Return early if exists and no custom config is passed\n\tif ok && options.Keypair == nil && len(options.CACrt) == 0 && len(options.Username+options.Password) == 0 {\n\t\tb.logger.Successf(\"source secret up to date\")\n\t\treturn nil\n\t}\n\n\t// Generate source secret\n\tb.logger.Actionf(\"generating source secret\")\n\tmanifest, err := sourcesecret.GenerateGit(options)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar secret corev1.Secret\n\tif err := yaml.Unmarshal([]byte(manifest.Content), &secret); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal generated source secret manifest: %w\", err)\n\t}\n\n\tfor _, callback := range b.postGenerateSecret {\n\t\tif err = callback(ctx, secret, options); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Apply source secret\n\tb.logger.Actionf(\"applying source secret %q\", secretKey)\n\tif err = reconcileSecret(ctx, b.kube, secret); err != nil {\n\t\treturn err\n\t}\n\tb.logger.Successf(\"reconciled source secret\")\n\n\treturn nil\n}\n\nfunc (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options sync.Options) error {\n\t// Confirm that sync configuration does not overwrite existing config\n\tif curPath, err := kustomizationPathDiffers(ctx, b.kube, client.ObjectKey{Name: options.Name, Namespace: options.Namespace}, options.TargetPath); err != nil {\n\t\treturn fmt.Errorf(\"failed to determine if sync configuration would overwrite existing Kustomization: %w\", err)\n\t} else if curPath != \"\" {\n\t\treturn fmt.Errorf(\"sync path configuration (%q) would overwrite path (%q) of existing Kustomization\", options.TargetPath, curPath)\n\t}\n\n\t// Clone if not already\n\tif _, err := b.gitClient.Head(); err != nil {\n\t\tif err == git.ErrNoGitRepository {\n\t\t\tb.logger.Actionf(\"cloning branch %q from Git repository %q\", b.branch, b.url)\n\t\t\tvar cloned bool\n\t\t\tif err = retry(1, 2*time.Second, func() (err error) {\n\t\t\t\tif err = b.cleanGitRepoDir(); err != nil {\n\t\t\t\t\tb.logger.Warningf(\" failed to clean directory for git repo: %w\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t_, err = b.gitClient.Clone(ctx, b.url, repository.CloneConfig{\n\t\t\t\t\tCheckoutStrategy: repository.CheckoutStrategy{\n\t\t\t\t\t\tBranch: b.branch,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.logger.Warningf(\" clone failure: %s\", err)\n\t\t\t\t}\n\t\t\t\tif err == nil {\n\t\t\t\t\tcloned = true\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to clone repository: %w\", err)\n\t\t\t}\n\t\t\tif cloned {\n\t\t\t\tb.logger.Successf(\"cloned repository\")\n\t\t\t}\n\t\t}\n\t\treturn err\n\t}\n\n\t// Generate sync manifests and write to Git repository\n\tb.logger.Actionf(\"generating sync manifests\")\n\tmanifests, err := sync.Generate(options)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"sync manifests generation failed: %w\", err)\n\t}\n\n\t// Create secure Kustomize FS\n\tfs, err := filesys.MakeFsOnDiskSecureBuild(b.gitClient.Path())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to initialize Kustomize file system: %w\", err)\n\t}\n\n\tif err = fs.WriteFile(filepath.Join(b.gitClient.Path(), manifests.Path), []byte(manifests.Content)); err != nil {\n\t\treturn err\n\t}\n\n\t// Generate Kustomization\n\tkusManifests, err := kustomization.Generate(kustomization.Options{\n\t\tFileSystem: fs,\n\t\tBaseDir:    b.gitClient.Path(),\n\t\tTargetPath: filepath.Dir(manifests.Path),\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s generation failed: %w\", konfig.DefaultKustomizationFileName(), err)\n\t}\n\tb.logger.Successf(\"generated sync manifests\")\n\n\t// Write generated files and make a commit\n\tvar signer *openpgp.Entity\n\tif b.gpgKeyRing != nil {\n\t\tsigner, err = getOpenPgpEntity(b.gpgKeyRing, b.gpgPassphrase, b.gpgKeyID)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to generate OpenPGP entity: %w\", err)\n\t\t}\n\t}\n\tcommitMsg := \"Add Flux sync manifests\"\n\tif b.commitMessageAppendix != \"\" {\n\t\tcommitMsg = commitMsg + \"\\n\\n\" + b.commitMessageAppendix\n\t}\n\n\tcommit, err := b.gitClient.Commit(git.Commit{\n\t\tAuthor:  b.signature,\n\t\tMessage: commitMsg,\n\t}, repository.WithFiles(map[string]io.Reader{\n\t\tkusManifests.Path: strings.NewReader(kusManifests.Content),\n\t}), repository.WithSigner(signer))\n\tif err != nil && err != git.ErrNoStagedFiles {\n\t\treturn fmt.Errorf(\"failed to commit sync manifests: %w\", err)\n\t}\n\n\tif err == nil {\n\t\tb.logger.Successf(\"committed sync manifests to %q (%q)\", b.branch, commit)\n\t\tb.logger.Actionf(\"pushing sync manifests to %q\", b.url)\n\t\terr = b.gitClient.Push(ctx, repository.PushConfig{})\n\t\tif err != nil {\n\t\t\tif strings.HasPrefix(err.Error(), gogit.ErrNonFastForwardUpdate.Error()) {\n\t\t\t\tb.logger.Waitingf(\"git conflict detected, retrying with a fresh clone\")\n\t\t\t\tif err := os.RemoveAll(b.gitClient.Path()); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to remove tmp dir: %w\", err)\n\t\t\t\t}\n\t\t\t\tif err := os.Mkdir(b.gitClient.Path(), 0o700); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to recreate tmp dir: %w\", err)\n\t\t\t\t}\n\t\t\t\tif err = retry(1, 2*time.Second, func() (err error) {\n\t\t\t\t\tif err = b.cleanGitRepoDir(); err != nil {\n\t\t\t\t\t\tb.logger.Warningf(\" failed to clean directory for git repo: %w\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t_, err = b.gitClient.Clone(ctx, b.url, repository.CloneConfig{\n\t\t\t\t\t\tCheckoutStrategy: repository.CheckoutStrategy{\n\t\t\t\t\t\t\tBranch: b.branch,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tb.logger.Warningf(\" clone failure: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to clone repository: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn b.ReconcileSyncConfig(ctx, options)\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"failed to push sync manifests: %w\", err)\n\t\t}\n\t} else {\n\t\tb.logger.Successf(\"sync manifests are up to date\")\n\t}\n\n\t// Apply to cluster\n\tb.logger.Actionf(\"applying sync manifests\")\n\tif _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.gitClient.Path(), filepath.Join(b.gitClient.Path(), kusManifests.Path)); err != nil {\n\t\treturn err\n\t}\n\n\tb.logger.Successf(\"reconciled sync configuration\")\n\n\treturn nil\n}\n\nfunc (b *PlainGitBootstrapper) ReportKustomizationHealth(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error {\n\thead, err := b.gitClient.Head()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tobjKey := client.ObjectKey{Name: options.Name, Namespace: options.Namespace}\n\n\texpectRevision := fmt.Sprintf(\"%s@%s\", options.Branch, git.Hash(head).Digest())\n\tb.logger.Waitingf(\"waiting for Kustomization %q to be reconciled\", objKey.String())\n\tk := &kustomizev1.Kustomization{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind: kustomizev1.KustomizationKind,\n\t\t},\n\t}\n\tif err := wait.PollUntilContextTimeout(ctx, pollInterval, timeout, true,\n\t\tobjectReconciled(b.kube, objKey, k, expectRevision)); err != nil {\n\t\t// If the poll timed out, we want to log the ready condition message as\n\t\t// that likely contains the reason\n\t\tif errors.Is(err, context.DeadlineExceeded) {\n\t\t\treadyCondition := apimeta.FindStatusCondition(k.Status.Conditions, meta.ReadyCondition)\n\t\t\tif readyCondition != nil && readyCondition.Status != metav1.ConditionTrue {\n\t\t\t\terr = fmt.Errorf(\"kustomization '%s' not ready: '%s'\", objKey, readyCondition.Message)\n\t\t\t}\n\t\t}\n\t\tb.logger.Failuref(err.Error())\n\t\treturn fmt.Errorf(\"error while waiting for Kustomization to be ready: '%s'\", err)\n\t}\n\tb.logger.Successf(\"Kustomization reconciled successfully\")\n\treturn nil\n}\n\nfunc (b *PlainGitBootstrapper) ReportGitRepoHealth(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error {\n\thead, err := b.gitClient.Head()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tobjKey := client.ObjectKey{Name: options.Name, Namespace: options.Namespace}\n\n\tb.logger.Waitingf(\"waiting for GitRepository %q to be reconciled\", objKey.String())\n\texpectRevision := fmt.Sprintf(\"%s@%s\", options.Branch, git.Hash(head).Digest())\n\tg := &sourcev1.GitRepository{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       sourcev1.GitRepositoryKind,\n\t\t\tAPIVersion: sourcev1.GroupVersion.String(),\n\t\t},\n\t}\n\tif err := wait.PollUntilContextTimeout(ctx, pollInterval, timeout, true,\n\t\tobjectReconciled(b.kube, objKey, g, expectRevision)); err != nil {\n\t\t// If the poll timed out, we want to log the ready condition message as\n\t\t// that likely contains the reason\n\t\tif errors.Is(err, context.DeadlineExceeded) {\n\t\t\treadyCondition := apimeta.FindStatusCondition(g.Status.Conditions, meta.ReadyCondition)\n\t\t\tif readyCondition != nil && readyCondition.Status != metav1.ConditionTrue {\n\t\t\t\terr = fmt.Errorf(\"gitrepository '%s' not ready: '%s'\", objKey, readyCondition.Message)\n\t\t\t}\n\t\t}\n\t\tb.logger.Failuref(err.Error())\n\t\treturn fmt.Errorf(\"error while waiting for GitRepository to be ready: '%s'\", err)\n\t}\n\tb.logger.Successf(\"GitRepository reconciled successfully\")\n\treturn nil\n}\nfunc (b *PlainGitBootstrapper) ReportComponentsHealth(ctx context.Context, install install.Options, timeout time.Duration) error {\n\tcfg, err := utils.KubeConfig(b.restClientGetter, b.restClientOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tchecker, err := status.NewStatusChecker(cfg, 5*time.Second, timeout, b.logger)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar components = install.Components\n\tcomponents = append(components, install.ComponentsExtra...)\n\n\tvar identifiers []object.ObjMetadata\n\tfor _, component := range components {\n\t\tidentifiers = append(identifiers, object.ObjMetadata{\n\t\t\tNamespace: install.Namespace,\n\t\t\tName:      component,\n\t\t\tGroupKind: schema.GroupKind{Group: \"apps\", Kind: \"Deployment\"},\n\t\t})\n\t}\n\n\tb.logger.Actionf(\"confirming components are healthy\")\n\tif err := checker.Assess(identifiers...); err != nil {\n\t\treturn err\n\t}\n\tb.logger.Successf(\"all components are healthy\")\n\treturn nil\n}\n\n// cleanGitRepoDir cleans the directory meant for the Git repo.\nfunc (b *PlainGitBootstrapper) cleanGitRepoDir() error {\n\tdirs, err := os.ReadDir(b.gitClient.Path())\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar errs []error\n\tfor _, dir := range dirs {\n\t\tif err := os.RemoveAll(filepath.Join(b.gitClient.Path(), dir.Name())); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\treturn errors.Join(errs...)\n}\n\nfunc getOpenPgpEntity(keyRing openpgp.EntityList, passphrase, keyID string) (*openpgp.Entity, error) {\n\tif len(keyRing) == 0 {\n\t\treturn nil, fmt.Errorf(\"empty GPG key ring\")\n\t}\n\n\tvar entity *openpgp.Entity\n\tif keyID != \"\" {\n\t\tkeyID = strings.TrimPrefix(keyID, \"0x\")\n\t\tif len(keyID) != 16 {\n\t\t\treturn nil, fmt.Errorf(\"invalid GPG key id length; expected %d, got %d\", 16, len(keyID))\n\t\t}\n\t\tkeyID = strings.ToUpper(keyID)\n\n\t\tfor _, ent := range keyRing {\n\t\t\tif ent.PrimaryKey.KeyIdString() == keyID {\n\t\t\t\tentity = ent\n\t\t\t}\n\t\t}\n\n\t\tif entity == nil {\n\t\t\treturn nil, fmt.Errorf(\"no GPG keyring matching key id '%s' found\", keyID)\n\t\t}\n\t\tif entity.PrivateKey == nil {\n\t\t\treturn nil, fmt.Errorf(\"keyring does not contain private key for key id '%s'\", keyID)\n\t\t}\n\t} else {\n\t\tentity = keyRing[0]\n\t}\n\n\terr := entity.PrivateKey.Decrypt([]byte(passphrase))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to decrypt GPG private key: %w\", err)\n\t}\n\n\treturn entity, nil\n}\n"
  },
  {
    "path": "pkg/bootstrap/bootstrap_provider.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bootstrap\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/go-git-providers/gitprovider\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/bootstrap/provider\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync\"\n\t\"github.com/fluxcd/pkg/git/repository\"\n)\n\ntype GitProviderBootstrapper struct {\n\t*PlainGitBootstrapper\n\n\towner          string\n\trepositoryName string\n\trepository     gitprovider.UserRepository\n\n\tpersonal bool\n\n\tdescription   string\n\tdefaultBranch string\n\tvisibility    string\n\n\treconcile bool\n\n\tteams map[string]string\n\n\treadWriteKey bool\n\n\tbootstrapTransportType string\n\tsyncTransportType      string\n\n\tsshHostname string\n\n\tuseDeployTokenAuth bool\n\n\tprovider gitprovider.Client\n}\n\nfunc NewGitProviderBootstrapper(git repository.Client, provider gitprovider.Client,\n\tkube client.Client, opts ...GitProviderOption) (*GitProviderBootstrapper, error) {\n\tb := &GitProviderBootstrapper{\n\t\tPlainGitBootstrapper: &PlainGitBootstrapper{\n\t\t\tgitClient: git,\n\t\t\tkube:      kube,\n\t\t},\n\t\tbootstrapTransportType: \"https\",\n\t\tsyncTransportType:      \"ssh\",\n\t\tprovider:               provider,\n\t}\n\tb.PlainGitBootstrapper.postGenerateSecret = append(b.PlainGitBootstrapper.postGenerateSecret, b.reconcileDeployKey)\n\tfor _, opt := range opts {\n\t\topt.applyGitProvider(b)\n\t}\n\treturn b, nil\n}\n\ntype GitProviderOption interface {\n\tapplyGitProvider(b *GitProviderBootstrapper)\n}\n\nfunc WithProviderRepository(owner, repositoryName string, personal bool) GitProviderOption {\n\treturn providerRepositoryOption{\n\t\towner:          owner,\n\t\trepositoryName: repositoryName,\n\t\tpersonal:       personal,\n\t}\n}\n\nfunc WithProviderVisibility(visibility string) GitProviderOption {\n\treturn providerRepositoryConfigOption{\n\t\tvisibility: visibility,\n\t}\n}\n\ntype providerRepositoryOption struct {\n\towner          string\n\trepositoryName string\n\tpersonal       bool\n}\n\nfunc (o providerRepositoryOption) applyGitProvider(b *GitProviderBootstrapper) {\n\tb.owner = o.owner\n\tb.repositoryName = o.repositoryName\n\tb.personal = o.personal\n}\n\nfunc WithProviderRepositoryConfig(description, defaultBranch, visibility string) GitProviderOption {\n\treturn providerRepositoryConfigOption{\n\t\tdescription:   description,\n\t\tdefaultBranch: defaultBranch,\n\t\tvisibility:    visibility,\n\t}\n}\n\ntype providerRepositoryConfigOption struct {\n\tdescription   string\n\tdefaultBranch string\n\tvisibility    string\n}\n\nfunc (o providerRepositoryConfigOption) applyGitProvider(b *GitProviderBootstrapper) {\n\tb.description = o.description\n\tb.defaultBranch = o.defaultBranch\n\tb.visibility = o.visibility\n}\n\nfunc WithProviderTeamPermissions(teams map[string]string) GitProviderOption {\n\treturn providerRepositoryTeamPermissionsOption(teams)\n}\n\ntype providerRepositoryTeamPermissionsOption map[string]string\n\nfunc (o providerRepositoryTeamPermissionsOption) applyGitProvider(b *GitProviderBootstrapper) {\n\tb.teams = o\n}\n\nfunc WithReadWriteKeyPermissions(b bool) GitProviderOption {\n\treturn withReadWriteKeyPermissionsOption(b)\n}\n\ntype withReadWriteKeyPermissionsOption bool\n\nfunc (o withReadWriteKeyPermissionsOption) applyGitProvider(b *GitProviderBootstrapper) {\n\tb.readWriteKey = bool(o)\n}\n\nfunc WithBootstrapTransportType(protocol string) GitProviderOption {\n\treturn bootstrapTransportTypeOption(protocol)\n}\n\ntype bootstrapTransportTypeOption string\n\nfunc (o bootstrapTransportTypeOption) applyGitProvider(b *GitProviderBootstrapper) {\n\tb.bootstrapTransportType = string(o)\n}\n\nfunc WithSyncTransportType(protocol string) GitProviderOption {\n\treturn syncProtocolOption(protocol)\n}\n\ntype syncProtocolOption string\n\nfunc (o syncProtocolOption) applyGitProvider(b *GitProviderBootstrapper) {\n\tb.syncTransportType = string(o)\n}\n\nfunc WithSSHHostname(hostname string) GitProviderOption {\n\treturn sshHostnameOption(hostname)\n}\n\ntype sshHostnameOption string\n\nfunc (o sshHostnameOption) applyGitProvider(b *GitProviderBootstrapper) {\n\tb.sshHostname = string(o)\n}\n\nfunc WithReconcile() GitProviderOption {\n\treturn reconcileOption(true)\n}\n\ntype reconcileOption bool\n\nfunc (o reconcileOption) applyGitProvider(b *GitProviderBootstrapper) {\n\tb.reconcile = true\n}\n\nfunc WithDeployTokenAuth() GitProviderOption {\n\treturn deployTokenAuthOption(true)\n}\n\ntype deployTokenAuthOption bool\n\nfunc (o deployTokenAuthOption) applyGitProvider(b *GitProviderBootstrapper) {\n\tb.useDeployTokenAuth = true\n}\n\nfunc (b *GitProviderBootstrapper) ReconcileSyncConfig(ctx context.Context, options sync.Options) error {\n\tif b.repository == nil {\n\t\treturn errors.New(\"repository is required\")\n\t}\n\n\tif b.url == \"\" {\n\t\tbootstrapURL, err := b.getCloneURL(b.repository, gitprovider.TransportType(b.bootstrapTransportType))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tWithRepositoryURL(bootstrapURL).applyGit(b.PlainGitBootstrapper)\n\t}\n\tif options.URL == \"\" {\n\t\tsyncURL, err := b.getCloneURL(b.repository, gitprovider.TransportType(b.syncTransportType))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\toptions.URL = syncURL\n\t}\n\n\treturn b.PlainGitBootstrapper.ReconcileSyncConfig(ctx, options)\n}\n\nfunc (b *GitProviderBootstrapper) ReconcileSourceSecret(ctx context.Context, options sourcesecret.Options) error {\n\tif b.repository == nil {\n\t\treturn errors.New(\"repository is required\")\n\t}\n\n\tif b.useDeployTokenAuth {\n\t\tdeployTokenInfo, err := b.reconcileDeployToken(ctx, options)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif deployTokenInfo != nil {\n\t\t\toptions.Username = deployTokenInfo.Username\n\t\t\toptions.Password = deployTokenInfo.Token\n\t\t}\n\t}\n\n\treturn b.PlainGitBootstrapper.ReconcileSourceSecret(ctx, options)\n}\n\n// ReconcileRepository reconciles an organization or user repository with the\n// GitProviderBootstrapper configuration. On success, the URL in the embedded\n// PlainGitBootstrapper is set to clone URL for the configured protocol.\n//\n// When part of the reconciliation fails with a warning without aborting, an\n// ErrReconciledWithWarning error is returned.\nfunc (b *GitProviderBootstrapper) ReconcileRepository(ctx context.Context) error {\n\tvar repo gitprovider.UserRepository\n\tvar err error\n\tif b.personal {\n\t\trepo, err = b.reconcileUserRepository(ctx)\n\t} else {\n\t\trepo, err = b.reconcileOrgRepository(ctx)\n\t}\n\tif err != nil && !errors.Is(err, ErrReconciledWithWarning) {\n\t\treturn err\n\t}\n\n\tcloneURL, err := b.getCloneURL(repo, gitprovider.TransportType(b.bootstrapTransportType))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tb.repository = repo\n\tWithRepositoryURL(cloneURL).applyGit(b.PlainGitBootstrapper)\n\n\treturn err\n}\n\nfunc (b *GitProviderBootstrapper) reconcileDeployKey(ctx context.Context, secret corev1.Secret, options sourcesecret.Options) error {\n\tif b.repository == nil {\n\t\treturn errors.New(\"repository is required\")\n\t}\n\n\tppk, ok := secret.StringData[sourcesecret.PublicKeySecretKey]\n\tif !ok {\n\t\treturn nil\n\t}\n\tb.logger.Successf(\"public key: %s\", strings.TrimSpace(ppk))\n\n\tname := deployKeyName(options.Namespace, b.branch, options.Name, options.TargetPath)\n\tdeployKeyInfo := newDeployKeyInfo(name, ppk, b.readWriteKey)\n\n\t_, changed, err := b.repository.DeployKeys().Reconcile(ctx, deployKeyInfo)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif changed {\n\t\tb.logger.Successf(\"configured deploy key %q for %q\", deployKeyInfo.Name, b.repository.Repository().String())\n\t}\n\treturn nil\n}\n\nfunc (b *GitProviderBootstrapper) reconcileDeployToken(ctx context.Context, options sourcesecret.Options) (*gitprovider.DeployTokenInfo, error) {\n\tdts, err := b.repository.DeployTokens()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tb.logger.Actionf(\"checking to reconcile deploy token for source secret\")\n\tname := deployTokenName(options.Namespace, b.branch, options.Name, options.TargetPath)\n\tdeployTokenInfo := gitprovider.DeployTokenInfo{Name: name}\n\n\tdeployToken, changed, err := dts.Reconcile(ctx, deployTokenInfo)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif changed {\n\t\tb.logger.Successf(\"configured deploy token %q for %q\", deployTokenInfo.Name, b.repository.Repository().String())\n\t\tdeployTokenInfo := deployToken.Get()\n\t\treturn &deployTokenInfo, nil\n\t}\n\n\tb.logger.Successf(\"reconciled deploy token for source secret\")\n\n\treturn nil, nil\n}\n\n// reconcileOrgRepository reconciles a gitprovider.OrgRepository\n// with the GitProviderBootstrapper values, including any\n// gitprovider.TeamAccessInfo configurations.\n//\n// If one of the gitprovider.TeamAccessInfo does not reconcile\n// successfully, the gitprovider.UserRepository and an\n// ErrReconciledWithWarning error are returned.\nfunc (b *GitProviderBootstrapper) reconcileOrgRepository(ctx context.Context) (gitprovider.UserRepository, error) {\n\tb.logger.Actionf(\"connecting to %s\", b.provider.SupportedDomain())\n\n\t// Construct the repository and other configuration objects\n\t// go-git-provider likes to work with\n\tsubOrgs, repoName := splitSubOrganizationsFromRepositoryName(b.repositoryName)\n\torgRef, err := b.getOrganization(ctx, subOrgs)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create new Git repository %q: %w\", b.repositoryName, err)\n\t}\n\trepoRef := newOrgRepositoryRef(*orgRef, repoName)\n\trepoInfo := newRepositoryInfo(b.description, b.defaultBranch, b.visibility)\n\n\t// Reconcile the organization repository\n\trepo, err := b.provider.OrgRepositories().Get(ctx, repoRef)\n\tif err != nil {\n\t\tif !errors.Is(err, gitprovider.ErrNotFound) {\n\t\t\treturn nil, fmt.Errorf(\"failed to get Git repository %q: provider error: %w\", repoRef.String(), err)\n\t\t}\n\t\t// go-git-providers has at present some issues with the idempotency\n\t\t// of the available Reconcile methods, and setting e.g. the default\n\t\t// branch correctly. Resort to Create until this has been resolved.\n\t\trepo, err = b.provider.OrgRepositories().Create(ctx, repoRef, repoInfo)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create new Git repository %q: %w\", repoRef.String(), err)\n\t\t}\n\t\tb.logger.Successf(\"repository %q created\", repoRef.String())\n\t}\n\n\tvar changed bool\n\tif b.reconcile {\n\t\tif err = retry(1, 2*time.Second, func() (err error) {\n\t\t\tchanged, err = repo.Reconcile(ctx)\n\t\t\treturn\n\t\t}); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to reconcile Git repository %q: %w\", repoRef.String(), err)\n\t\t}\n\t\tif changed {\n\t\t\tb.logger.Successf(\"repository %q reconciled\", repoRef.String())\n\t\t}\n\t}\n\n\t// Build the team access config\n\tteamAccessInfo, err := buildTeamAccessInfo(b.teams, gitprovider.RepositoryPermissionVar(gitprovider.RepositoryPermissionMaintain))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to reconcile repository team access: %w\", err)\n\t}\n\n\t// Reconcile the team access config on best effort (that being:\n\t// record the error as a warning, but continue with the\n\t// reconciliation of the others)\n\tvar warning error\n\tif count := len(teamAccessInfo); count > 0 {\n\t\tb.logger.Actionf(\"reconciling repository permissions\")\n\t\tfor _, i := range teamAccessInfo {\n\t\t\tvar err error\n\t\t\t// Don't reconcile team if team already exists and b.reconcile is false\n\t\t\tif team, err := repo.TeamAccess().Get(ctx, i.Name); err == nil && !b.reconcile && team != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t_, changed, err = repo.TeamAccess().Reconcile(ctx, i)\n\t\t\tif err != nil {\n\t\t\t\twarning = fmt.Errorf(\"failed to grant permissions to team: %w\", ErrReconciledWithWarning)\n\t\t\t\tb.logger.Failuref(\"failed to grant %q permissions to %q: %s\", *i.Permission, i.Name, err.Error())\n\t\t\t} else if changed {\n\t\t\t\tb.logger.Successf(\"granted %q permissions to %q\", *i.Permission, i.Name)\n\t\t\t}\n\t\t}\n\t\tb.logger.Successf(\"reconciled repository permissions\")\n\t}\n\treturn repo, warning\n}\n\n// reconcileUserRepository reconciles a gitprovider.UserRepository\n// with the GitProviderBootstrapper values. It returns the reconciled\n// gitprovider.UserRepository, or an error.\nfunc (b *GitProviderBootstrapper) reconcileUserRepository(ctx context.Context) (gitprovider.UserRepository, error) {\n\tb.logger.Actionf(\"connecting to %s\", b.provider.SupportedDomain())\n\n\t// Construct the repository and other metadata objects\n\t// go-git-provider likes to work with.\n\t_, repoName := splitSubOrganizationsFromRepositoryName(b.repositoryName)\n\tuserRef := newUserRef(b.provider.SupportedDomain(), b.owner)\n\trepoRef := newUserRepositoryRef(userRef, repoName)\n\trepoInfo := newRepositoryInfo(b.description, b.defaultBranch, b.visibility)\n\n\t// Reconcile the user repository\n\trepo, err := b.provider.UserRepositories().Get(ctx, repoRef)\n\tif err != nil {\n\t\tif !errors.Is(err, gitprovider.ErrNotFound) {\n\t\t\treturn nil, fmt.Errorf(\"failed to get Git repository %q: provider error: %w\", repoRef.String(), err)\n\t\t}\n\t\t// go-git-providers has at present some issues with the idempotency\n\t\t// of the available Reconcile methods, and setting e.g. the default\n\t\t// branch correctly. Resort to Create until this has been resolved.\n\t\trepo, err = b.provider.UserRepositories().Create(ctx, repoRef, repoInfo)\n\t\tif err != nil {\n\t\t\tvar userErr *gitprovider.ErrIncorrectUser\n\t\t\tif errors.As(err, &userErr) {\n\t\t\t\t// return a better error message when the wrong owner is set\n\t\t\t\terr = fmt.Errorf(\"the specified owner '%s' doesn't match the identity associated with the given token\", b.owner)\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"failed to create new Git repository %q: %w\", repoRef.String(), err)\n\t\t}\n\t\tb.logger.Successf(\"repository %q created\", repoRef.String())\n\t}\n\n\tif b.reconcile {\n\t\tvar changed bool\n\t\tif err = retry(1, 2*time.Second, func() (err error) {\n\t\t\tchanged, err = repo.Reconcile(ctx)\n\t\t\treturn\n\t\t}); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to reconcile Git repository %q: %w\", repoRef.String(), err)\n\t\t}\n\t\tif changed {\n\t\t\tb.logger.Successf(\"repository %q reconciled\", repoRef.String())\n\t\t}\n\t}\n\n\treturn repo, nil\n}\n\n// getOrganization retrieves and returns the gitprovider.Organization\n// using the GitProviderBootstrapper values.\nfunc (b *GitProviderBootstrapper) getOrganization(ctx context.Context, subOrgs []string) (*gitprovider.OrganizationRef, error) {\n\torgRef := newOrganizationRef(b.provider.SupportedDomain(), b.owner, subOrgs)\n\t// With Stash get the organization to be sure to get the correct key\n\tif string(b.provider.ProviderID()) == string(provider.GitProviderStash) {\n\t\torg, err := b.provider.Organizations().Get(ctx, orgRef)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get Git organization: %w\", err)\n\t\t}\n\n\t\torgRef = org.Organization()\n\n\t\treturn &orgRef, nil\n\t}\n\treturn &orgRef, nil\n}\n\n// getCloneURL returns the Git clone URL for the given\n// gitprovider.UserRepository. If the given transport type is\n// gitprovider.TransportTypeSSH and a custom SSH hostname is configured,\n// the hostname of the URL will be modified to this hostname.\nfunc (b *GitProviderBootstrapper) getCloneURL(repository gitprovider.UserRepository, transport gitprovider.TransportType) (string, error) {\n\tvar url string\n\tif cloner, ok := repository.(gitprovider.CloneableURL); ok {\n\t\turl = cloner.GetCloneURL(\"\", transport)\n\t} else {\n\t\turl = repository.Repository().GetCloneURL(transport)\n\t}\n\n\tvar err error\n\tif transport == gitprovider.TransportTypeSSH && b.sshHostname != \"\" {\n\t\tif url, err = setHostname(url, b.sshHostname); err != nil {\n\t\t\terr = fmt.Errorf(\"failed to set SSH hostname for URL %q: %w\", url, err)\n\t\t}\n\t}\n\treturn url, err\n}\n\n// splitSubOrganizationsFromRepositoryName removes any prefixed sub\n// organizations from the given repository name by splitting the\n// string into a slice by '/'.\n// The last (or only) item of the slice result is assumed to be the\n// repository name, other items (nested) sub organizations.\nfunc splitSubOrganizationsFromRepositoryName(name string) ([]string, string) {\n\telements := strings.Split(name, \"/\")\n\ti := len(elements)\n\tswitch i {\n\tcase 1:\n\t\treturn nil, name\n\tdefault:\n\t\treturn elements[:i-1], elements[i-1]\n\t}\n}\n\n// buildTeamAccessInfo constructs a gitprovider.TeamAccessInfo slice\n// from the given string map of team names to permissions.\n//\n// Providing a default gitprovider.RepositoryPermission is optional,\n// and omitting it will make it default to the go-git-provider default.\n//\n// An error is returned if any of the given permissions is invalid.\nfunc buildTeamAccessInfo(m map[string]string, defaultPermissions *gitprovider.RepositoryPermission) ([]gitprovider.TeamAccessInfo, error) {\n\tvar infos []gitprovider.TeamAccessInfo\n\tif defaultPermissions != nil {\n\t\tif err := gitprovider.ValidateRepositoryPermission(*defaultPermissions); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid default team permission %q\", *defaultPermissions)\n\t\t}\n\t}\n\tfor n, p := range m {\n\t\tpermission := defaultPermissions\n\t\tif p != \"\" {\n\t\t\tp := gitprovider.RepositoryPermission(p)\n\t\t\tif err := gitprovider.ValidateRepositoryPermission(p); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid permission %q for team %q\", p, n)\n\t\t\t}\n\t\t\tpermission = &p\n\t\t}\n\t\ti := gitprovider.TeamAccessInfo{\n\t\t\tName:       n,\n\t\t\tPermission: permission,\n\t\t}\n\t\tinfos = append(infos, i)\n\t}\n\treturn infos, nil\n}\n\n// newOrganizationRef constructs a gitprovider.OrganizationRef with the\n// given values and returns the result.\nfunc newOrganizationRef(domain, organization string, subOrganizations []string) gitprovider.OrganizationRef {\n\treturn gitprovider.OrganizationRef{\n\t\tDomain:           domain,\n\t\tOrganization:     organization,\n\t\tSubOrganizations: subOrganizations,\n\t}\n}\n\n// newOrgRepositoryRef constructs a gitprovider.OrgRepositoryRef with\n// the given values and returns the result.\nfunc newOrgRepositoryRef(organizationRef gitprovider.OrganizationRef, name string) gitprovider.OrgRepositoryRef {\n\treturn gitprovider.OrgRepositoryRef{\n\t\tOrganizationRef: organizationRef,\n\t\tRepositoryName:  name,\n\t}\n}\n\n// newUserRef constructs a gitprovider.UserRef with the given values\n// and returns the result.\nfunc newUserRef(domain, login string) gitprovider.UserRef {\n\treturn gitprovider.UserRef{\n\t\tDomain:    domain,\n\t\tUserLogin: login,\n\t}\n}\n\n// newUserRepositoryRef constructs a gitprovider.UserRepositoryRef with\n// the given values and returns the result.\nfunc newUserRepositoryRef(userRef gitprovider.UserRef, name string) gitprovider.UserRepositoryRef {\n\treturn gitprovider.UserRepositoryRef{\n\t\tUserRef:        userRef,\n\t\tRepositoryName: name,\n\t}\n}\n\n// newRepositoryInfo constructs a gitprovider.RepositoryInfo with the\n// given values and returns the result.\nfunc newRepositoryInfo(description, defaultBranch, visibility string) gitprovider.RepositoryInfo {\n\tvar i gitprovider.RepositoryInfo\n\tif description != \"\" {\n\t\ti.Description = gitprovider.StringVar(description)\n\t}\n\tif defaultBranch != \"\" {\n\t\ti.DefaultBranch = gitprovider.StringVar(defaultBranch)\n\t}\n\tif visibility != \"\" {\n\t\ti.Visibility = gitprovider.RepositoryVisibilityVar(gitprovider.RepositoryVisibility(visibility))\n\t}\n\treturn i\n}\n\n// newDeployKeyInfo constructs a gitprovider.DeployKeyInfo with the\n// given values and returns the result.\nfunc newDeployKeyInfo(name, publicKey string, readWrite bool) gitprovider.DeployKeyInfo {\n\tkeyInfo := gitprovider.DeployKeyInfo{\n\t\tName: name,\n\t\tKey:  []byte(publicKey),\n\t}\n\tif readWrite {\n\t\tkeyInfo.ReadOnly = gitprovider.BoolVar(false)\n\t}\n\treturn keyInfo\n}\n\nfunc deployKeyName(namespace, secretName, branch, path string) string {\n\tvar name string\n\tfor _, v := range []string{namespace, secretName, branch, path} {\n\t\tif v == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif name == \"\" {\n\t\t\tname = v\n\t\t} else {\n\t\t\tname = name + \"-\" + v\n\t\t}\n\t}\n\treturn name\n}\n\nfunc deployTokenName(namespace, secretName, branch, path string) string {\n\tvar elems []string\n\tfor _, v := range []string{namespace, secretName, branch, path} {\n\t\tif v == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\telems = append(elems, v)\n\t}\n\treturn strings.Join(elems, \"-\")\n}\n\n// setHostname is a helper to replace the hostname of the given URL.\n// TODO(hidde): support for this should be added in go-git-providers.\nfunc setHostname(URL, hostname string) (string, error) {\n\tu, err := url.Parse(URL)\n\tif err != nil {\n\t\treturn URL, err\n\t}\n\tu.Host = hostname\n\treturn u.String(), nil\n}\n"
  },
  {
    "path": "pkg/bootstrap/bootstrap_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bootstrap\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\t. \"github.com/onsi/gomega\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client/fake\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1beta2\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/internal/utils\"\n)\n\nfunc Test_hasRevision(t *testing.T) {\n\tvar revision = \"main@sha1:5bf3a8f9bb0aa5ae8afd6208f43757ab73fc033a\"\n\n\ttests := []struct {\n\t\tname         string\n\t\tobj          objectWithConditions\n\t\trev          string\n\t\texpectErr    bool\n\t\texpectedBool bool\n\t}{\n\t\t{\n\t\t\tname: \"Kustomization revision\",\n\t\t\tobj: &kustomizev1.Kustomization{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind: kustomizev1.KustomizationKind,\n\t\t\t\t},\n\t\t\t\tStatus: kustomizev1.KustomizationStatus{\n\t\t\t\t\tLastAttemptedRevision: \"main@sha1:5bf3a8f9bb0aa5ae8afd6208f43757ab73fc033a\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedBool: true,\n\t\t},\n\t\t{\n\t\t\tname: \"GitRepository revision\",\n\t\t\tobj: &sourcev1.GitRepository{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       sourcev1.GitRepositoryKind,\n\t\t\t\t\tAPIVersion: sourcev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tStatus: sourcev1.GitRepositoryStatus{\n\t\t\t\t\tArtifact: &meta.Artifact{\n\t\t\t\t\t\tRevision: \"main@sha1:5bf3a8f9bb0aa5ae8afd6208f43757ab73fc033a\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedBool: true,\n\t\t},\n\t\t{\n\t\t\tname: \"GitRepository revision (wrong revision)\",\n\t\t\tobj: &sourcev1.GitRepository{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       sourcev1.GitRepositoryKind,\n\t\t\t\t\tAPIVersion: sourcev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tStatus: sourcev1.GitRepositoryStatus{\n\t\t\t\t\tArtifact: &meta.Artifact{\n\t\t\t\t\t\tRevision: \"main@sha1:e7f3a8f9bb0aa5ae8afd6208f43757ab73fc043a\",\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: \"Kustomization revision (empty revision)\",\n\t\t\tobj: &kustomizev1.Kustomization{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind: kustomizev1.KustomizationKind,\n\t\t\t\t},\n\t\t\t\tStatus: kustomizev1.KustomizationStatus{\n\t\t\t\t\tLastAttemptedRevision: \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"OCIRepository revision\",\n\t\t\tobj: &sourcev1.OCIRepository{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind: sourcev1.OCIRepositoryKind,\n\t\t\t\t},\n\t\t\t\tStatus: sourcev1.OCIRepositoryStatus{\n\t\t\t\t\tArtifact: &meta.Artifact{\n\t\t\t\t\t\tRevision: \"main@sha1:5bf3a8f9bb0aa5ae8afd6208f43757ab73fc033a\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedBool: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Alert revision(Not supported)\",\n\t\t\tobj: &notificationv1.Alert{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind: notificationv1.AlertKind,\n\t\t\t\t},\n\t\t\t\tStatus: notificationv1.AlertStatus{\n\t\t\t\t\tObservedGeneration: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tg := NewWithT(t)\n\t\t\tobj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tt.obj)\n\t\t\tg.Expect(err).To(BeNil())\n\t\t\tgot, err := hasRevision(tt.obj.GetObjectKind().GroupVersionKind().Kind, obj, revision)\n\t\t\tif tt.expectErr {\n\t\t\t\tg.Expect(err).To(HaveOccurred())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tg.Expect(got).To(Equal(tt.expectedBool))\n\t\t})\n\t}\n}\n\nfunc Test_objectReconciled(t *testing.T) {\n\texpectedRev := \"main@sha1:5bf3a8f9bb0aa5ae8afd6208f43757ab73fc033a\"\n\n\ttype updateStatus struct {\n\t\tstatusFn     func(o client.Object)\n\t\texpectedErr  bool\n\t\texpectedBool bool\n\t}\n\ttests := []struct {\n\t\tname     string\n\t\tobj      objectWithConditions\n\t\tstatuses []updateStatus\n\t}{\n\t\t{\n\t\t\tname: \"GitRepository with no status\",\n\t\t\tobj: &sourcev1.GitRepository{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       sourcev1.GitRepositoryKind,\n\t\t\t\t\tAPIVersion: sourcev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"flux-system\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tstatuses: []updateStatus{\n\t\t\t\t{\n\t\t\t\t\texpectedErr:  false,\n\t\t\t\t\texpectedBool: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"suspended Kustomization\",\n\t\t\tobj: &kustomizev1.Kustomization{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       kustomizev1.KustomizationKind,\n\t\t\t\t\tAPIVersion: kustomizev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"flux-system\",\n\t\t\t\t\tNamespace: \"flux-system\",\n\t\t\t\t},\n\t\t\t\tSpec: kustomizev1.KustomizationSpec{\n\t\t\t\t\tSuspend: true,\n\t\t\t\t},\n\t\t\t},\n\t\t\tstatuses: []updateStatus{\n\t\t\t\t{\n\t\t\t\t\texpectedErr: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Kustomization - status with old generation\",\n\t\t\tobj: &kustomizev1.Kustomization{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       kustomizev1.KustomizationKind,\n\t\t\t\t\tAPIVersion: kustomizev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:       \"flux-system\",\n\t\t\t\t\tNamespace:  \"flux-system\",\n\t\t\t\t\tGeneration: 1,\n\t\t\t\t},\n\t\t\t\tStatus: kustomizev1.KustomizationStatus{\n\t\t\t\t\tObservedGeneration: -1,\n\t\t\t\t},\n\t\t\t},\n\t\t\tstatuses: []updateStatus{\n\t\t\t\t{\n\t\t\t\t\texpectedErr:  false,\n\t\t\t\t\texpectedBool: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"GitRepository - status with same generation but no conditions\",\n\t\t\tobj: &sourcev1.GitRepository{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       sourcev1.GitRepositoryKind,\n\t\t\t\t\tAPIVersion: sourcev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:       \"flux-system\",\n\t\t\t\t\tNamespace:  \"flux-system\",\n\t\t\t\t\tGeneration: 1,\n\t\t\t\t},\n\t\t\t\tStatus: sourcev1.GitRepositoryStatus{\n\t\t\t\t\tObservedGeneration: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\tstatuses: []updateStatus{\n\t\t\t\t{\n\t\t\t\t\texpectedErr:  false,\n\t\t\t\t\texpectedBool: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"GitRepository - status with conditions but no ready condition\",\n\t\t\tobj: &sourcev1.GitRepository{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       sourcev1.GitRepositoryKind,\n\t\t\t\t\tAPIVersion: sourcev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:       \"flux-system\",\n\t\t\t\t\tNamespace:  \"flux-system\",\n\t\t\t\t\tGeneration: 1,\n\t\t\t\t},\n\t\t\t\tStatus: sourcev1.GitRepositoryStatus{\n\t\t\t\t\tObservedGeneration: 1,\n\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t{Type: meta.ReconcilingCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: \"Progressing\", Message: \"Progressing\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tstatuses: []updateStatus{\n\t\t\t\t{\n\t\t\t\t\texpectedErr:  false,\n\t\t\t\t\texpectedBool: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Kustomization - status with false ready condition\",\n\t\t\tobj: &kustomizev1.Kustomization{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       kustomizev1.KustomizationKind,\n\t\t\t\t\tAPIVersion: kustomizev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:       \"flux-system\",\n\t\t\t\t\tNamespace:  \"flux-system\",\n\t\t\t\t\tGeneration: 1,\n\t\t\t\t},\n\t\t\t\tStatus: kustomizev1.KustomizationStatus{\n\t\t\t\t\tObservedGeneration: 1,\n\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t{Type: meta.ReadyCondition, Status: metav1.ConditionFalse, ObservedGeneration: 1, Reason: \"Failing\", Message: \"Failed to clone\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tstatuses: []updateStatus{\n\t\t\t\t{\n\t\t\t\t\texpectedErr:  true,\n\t\t\t\t\texpectedBool: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Kustomization - status with true ready condition but different revision\",\n\t\t\tobj: &kustomizev1.Kustomization{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       kustomizev1.KustomizationKind,\n\t\t\t\t\tAPIVersion: kustomizev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:       \"flux-system\",\n\t\t\t\t\tNamespace:  \"flux-system\",\n\t\t\t\t\tGeneration: 1,\n\t\t\t\t},\n\t\t\t\tStatus: kustomizev1.KustomizationStatus{\n\t\t\t\t\tObservedGeneration: 1,\n\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t{Type: meta.ReadyCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: \"Passing\", Message: \"Applied revision\"},\n\t\t\t\t\t},\n\t\t\t\t\tLastAttemptedRevision: \"main@sha1:e7f3a8f9bb0aa5ae8afd6208f43757ab73fc043a\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tstatuses: []updateStatus{\n\t\t\t\t{\n\t\t\t\t\texpectedErr:  false,\n\t\t\t\t\texpectedBool: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"GitRepository - status with true ready condition but different revision\",\n\t\t\tobj: &sourcev1.GitRepository{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       sourcev1.GitRepositoryKind,\n\t\t\t\t\tAPIVersion: sourcev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:       \"flux-system\",\n\t\t\t\t\tNamespace:  \"flux-system\",\n\t\t\t\t\tGeneration: 1,\n\t\t\t\t},\n\t\t\t\tStatus: sourcev1.GitRepositoryStatus{\n\t\t\t\t\tObservedGeneration: 1,\n\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t{Type: meta.ReadyCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: \"Readyyy\", Message: \"Cloned successfully\"},\n\t\t\t\t\t},\n\t\t\t\t\tArtifact: &meta.Artifact{\n\t\t\t\t\t\tRevision: \"main@sha1:e7f3a8f9bb0aa5ae8afd6208f43757ab73fc043a\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tstatuses: []updateStatus{\n\t\t\t\t{\n\t\t\t\t\texpectedErr:  false,\n\t\t\t\t\texpectedBool: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"GitRepository - ready with right revision\",\n\t\t\tobj: &sourcev1.GitRepository{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       sourcev1.GitRepositoryKind,\n\t\t\t\t\tAPIVersion: sourcev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:       \"flux-system\",\n\t\t\t\t\tNamespace:  \"flux-system\",\n\t\t\t\t\tGeneration: 1,\n\t\t\t\t},\n\t\t\t\tStatus: sourcev1.GitRepositoryStatus{\n\t\t\t\t\tObservedGeneration: 1,\n\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t{Type: meta.ReadyCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: \"Readyyy\", Message: \"Cloned successfully\"},\n\t\t\t\t\t},\n\t\t\t\t\tArtifact: &meta.Artifact{\n\t\t\t\t\t\tRevision: expectedRev,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tstatuses: []updateStatus{\n\t\t\t\t{\n\t\t\t\t\texpectedErr:  false,\n\t\t\t\t\texpectedBool: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"GitRepository - sequence of status updates before ready\",\n\t\t\tobj: &sourcev1.GitRepository{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       sourcev1.GitRepositoryKind,\n\t\t\t\t\tAPIVersion: sourcev1.GroupVersion.String(),\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:       \"flux-system\",\n\t\t\t\t\tNamespace:  \"flux-system\",\n\t\t\t\t\tGeneration: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\tstatuses: []updateStatus{\n\t\t\t\t{\n\t\t\t\t\t// observed gen different\n\t\t\t\t\tstatusFn: func(o client.Object) {\n\t\t\t\t\t\tgitRepo := o.(*sourcev1.GitRepository)\n\t\t\t\t\t\tgitRepo.Status = sourcev1.GitRepositoryStatus{\n\t\t\t\t\t\t\tObservedGeneration: -1,\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t// ready failing\n\t\t\t\t\tstatusFn: func(o client.Object) {\n\t\t\t\t\t\tgitRepo := o.(*sourcev1.GitRepository)\n\t\t\t\t\t\tgitRepo.Status = sourcev1.GitRepositoryStatus{\n\t\t\t\t\t\t\tObservedGeneration: 1,\n\t\t\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t\t\t{Type: meta.ReadyCondition, Status: metav1.ConditionFalse, ObservedGeneration: 1, Reason: \"Not Ready\", Message: \"Transient connection issue\"},\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\texpectedErr: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t// updated to a different revision\n\t\t\t\t\tstatusFn: func(o client.Object) {\n\t\t\t\t\t\tgitRepo := o.(*sourcev1.GitRepository)\n\t\t\t\t\t\tgitRepo.Status = sourcev1.GitRepositoryStatus{\n\t\t\t\t\t\t\tObservedGeneration: 1,\n\t\t\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t\t\t{Type: meta.ReadyCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: \"Readyyy\", Message: \"Cloned successfully\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tArtifact: &meta.Artifact{\n\t\t\t\t\t\t\t\tRevision: \"wrong rev\",\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\t{\n\t\t\t\t\t// updated to the expected revision\n\t\t\t\t\tstatusFn: func(o client.Object) {\n\t\t\t\t\t\tgitRepo := o.(*sourcev1.GitRepository)\n\t\t\t\t\t\tgitRepo.Status = sourcev1.GitRepositoryStatus{\n\t\t\t\t\t\t\tObservedGeneration: 1,\n\t\t\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t\t\t{Type: meta.ReadyCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: \"Readyyy\", Message: \"Cloned successfully\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tArtifact: &meta.Artifact{\n\t\t\t\t\t\t\t\tRevision: expectedRev,\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\texpectedBool: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tg := NewWithT(t)\n\t\t\tbuilder := fake.NewClientBuilder().WithScheme(utils.NewScheme())\n\t\t\tbuilder.WithObjects(tt.obj)\n\n\t\t\tkubeClient := builder.Build()\n\n\t\t\tfor _, updates := range tt.statuses {\n\t\t\t\tif updates.statusFn != nil {\n\t\t\t\t\tupdates.statusFn(tt.obj)\n\t\t\t\t\tcloneObj := tt.obj.DeepCopyObject().(client.Object)\n\t\t\t\t\tg.Expect(kubeClient.Update(context.TODO(), cloneObj)).To(Succeed())\n\t\t\t\t}\n\n\t\t\t\twaitFunc := objectReconciled(kubeClient, client.ObjectKeyFromObject(tt.obj), tt.obj, expectedRev)\n\t\t\t\tgot, err := waitFunc(context.TODO())\n\t\t\t\tg.Expect(err != nil).To(Equal(updates.expectedErr), \"unexpected error: %v, for: %v\", err, tt.obj)\n\t\t\t\tg.Expect(got).To(Equal(updates.expectedBool))\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/bootstrap/options.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage bootstrap\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\n\t\"github.com/ProtonMail/go-crypto/openpgp\"\n\t\"github.com/fluxcd/pkg/git\"\n\trunclient \"github.com/fluxcd/pkg/runtime/client\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/log\"\n)\n\ntype Option interface {\n\tGitOption\n\tGitProviderOption\n}\n\nfunc WithBranch(branch string) Option {\n\treturn branchOption(branch)\n}\n\ntype branchOption string\n\nfunc (o branchOption) applyGit(b *PlainGitBootstrapper) {\n\tb.branch = string(o)\n}\n\nfunc (o branchOption) applyGitProvider(b *GitProviderBootstrapper) {\n\to.applyGit(b.PlainGitBootstrapper)\n}\n\nfunc WithSignature(name, email string) Option {\n\treturn signatureOption{\n\t\tName:  name,\n\t\tEmail: email,\n\t}\n}\n\ntype signatureOption git.Signature\n\nfunc (o signatureOption) applyGit(b *PlainGitBootstrapper) {\n\tif o.Name != \"\" {\n\t\tb.signature.Name = o.Name\n\t}\n\tif o.Email != \"\" {\n\t\tb.signature.Email = o.Email\n\t}\n}\n\nfunc (o signatureOption) applyGitProvider(b *GitProviderBootstrapper) {\n\to.applyGit(b.PlainGitBootstrapper)\n}\n\nfunc WithCommitMessageAppendix(appendix string) Option {\n\treturn commitMessageAppendixOption(appendix)\n}\n\ntype commitMessageAppendixOption string\n\nfunc (o commitMessageAppendixOption) applyGit(b *PlainGitBootstrapper) {\n\tb.commitMessageAppendix = string(o)\n}\n\nfunc (o commitMessageAppendixOption) applyGitProvider(b *GitProviderBootstrapper) {\n\to.applyGit(b.PlainGitBootstrapper)\n}\n\nfunc WithKubeconfig(rcg genericclioptions.RESTClientGetter, opts *runclient.Options) Option {\n\treturn kubeconfigOption{\n\t\trcg:  rcg,\n\t\topts: opts,\n\t}\n}\n\ntype kubeconfigOption struct {\n\trcg  genericclioptions.RESTClientGetter\n\topts *runclient.Options\n}\n\nfunc (o kubeconfigOption) applyGit(b *PlainGitBootstrapper) {\n\tb.restClientGetter = o.rcg\n\tb.restClientOptions = o.opts\n}\n\nfunc (o kubeconfigOption) applyGitProvider(b *GitProviderBootstrapper) {\n\to.applyGit(b.PlainGitBootstrapper)\n}\n\nfunc WithLogger(logger log.Logger) Option {\n\treturn loggerOption{logger}\n}\n\ntype loggerOption struct {\n\tlogger log.Logger\n}\n\nfunc (o loggerOption) applyGit(b *PlainGitBootstrapper) {\n\tb.logger = o.logger\n}\n\nfunc (o loggerOption) applyGitProvider(b *GitProviderBootstrapper) {\n\tb.logger = o.logger\n}\n\nfunc WithGitCommitSigning(gpgKeyRing openpgp.EntityList, passphrase, keyID string) Option {\n\treturn gitCommitSigningOption{\n\t\tgpgKeyRing:    gpgKeyRing,\n\t\tgpgPassphrase: passphrase,\n\t\tgpgKeyID:      keyID,\n\t}\n}\n\ntype gitCommitSigningOption struct {\n\tgpgKeyRing    openpgp.EntityList\n\tgpgPassphrase string\n\tgpgKeyID      string\n}\n\nfunc (o gitCommitSigningOption) applyGit(b *PlainGitBootstrapper) {\n\tb.gpgKeyRing = o.gpgKeyRing\n\tb.gpgPassphrase = o.gpgPassphrase\n\tb.gpgKeyID = o.gpgKeyID\n}\n\nfunc (o gitCommitSigningOption) applyGitProvider(b *GitProviderBootstrapper) {\n\to.applyGit(b.PlainGitBootstrapper)\n}\n\nfunc LoadEntityListFromPath(path string) (openpgp.EntityList, error) {\n\tif path == \"\" {\n\t\treturn nil, nil\n\t}\n\tr, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to open GPG key ring: %w\", err)\n\t}\n\tentityList, err := openpgp.ReadKeyRing(r)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to read GPG key ring: %w\", err)\n\t}\n\treturn entityList, nil\n}\n"
  },
  {
    "path": "pkg/bootstrap/provider/factory.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage provider\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/fluxcd/go-git-providers/gitea\"\n\t\"github.com/fluxcd/go-git-providers/github\"\n\t\"github.com/fluxcd/go-git-providers/gitlab\"\n\t\"github.com/fluxcd/go-git-providers/gitprovider\"\n\t\"github.com/fluxcd/go-git-providers/stash\"\n)\n\n// BuildGitProvider builds a gitprovider.Client for the provided\n// Config. It returns an error if the Config.Provider\n// is not supported, or if the construction of the client fails.\nfunc BuildGitProvider(config Config) (gitprovider.Client, error) {\n\tvar client gitprovider.Client\n\tvar err error\n\tswitch config.Provider {\n\tcase GitProviderGitHub:\n\t\topts := []gitprovider.ClientOption{\n\t\t\tgitprovider.WithOAuth2Token(config.Token),\n\t\t}\n\t\tif config.Hostname != \"\" {\n\t\t\topts = append(opts, gitprovider.WithDomain(config.Hostname))\n\t\t}\n\t\tif config.CaBundle != nil {\n\t\t\topts = append(opts, gitprovider.WithCustomCAPostChainTransportHook(config.CaBundle))\n\t\t}\n\t\tif client, err = github.NewClient(opts...); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase GitProviderGitea:\n\t\topts := []gitprovider.ClientOption{}\n\t\tif config.Hostname != \"\" {\n\t\t\topts = append(opts, gitprovider.WithDomain(config.Hostname))\n\t\t}\n\t\tif config.CaBundle != nil {\n\t\t\topts = append(opts, gitprovider.WithCustomCAPostChainTransportHook(config.CaBundle))\n\t\t}\n\t\tif client, err = gitea.NewClient(config.Token, opts...); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase GitProviderGitLab:\n\t\topts := []gitprovider.ClientOption{\n\t\t\tgitprovider.WithConditionalRequests(true),\n\t\t}\n\t\tif config.Hostname != \"\" {\n\t\t\topts = append(opts, gitprovider.WithDomain(config.Hostname))\n\t\t}\n\t\tif config.CaBundle != nil {\n\t\t\topts = append(opts, gitprovider.WithCustomCAPostChainTransportHook(config.CaBundle))\n\t\t}\n\t\tif client, err = gitlab.NewClient(config.Token, \"\", opts...); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase GitProviderStash:\n\t\topts := []gitprovider.ClientOption{}\n\t\tif config.Hostname != \"\" {\n\t\t\topts = append(opts, gitprovider.WithDomain(config.Hostname))\n\t\t}\n\t\tif config.CaBundle != nil {\n\t\t\topts = append(opts, gitprovider.WithCustomCAPostChainTransportHook(config.CaBundle))\n\t\t}\n\t\tif client, err = stash.NewStashClient(config.Username, config.Token, opts...); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Git provider '%s'\", config.Provider)\n\t}\n\treturn client, err\n}\n"
  },
  {
    "path": "pkg/bootstrap/provider/provider.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage provider\n\n// GitProvider holds a Git provider definition.\ntype GitProvider string\n\nconst (\n\tGitProviderGitHub GitProvider = \"github\"\n\tGitProviderGitea  GitProvider = \"gitea\"\n\tGitProviderGitLab GitProvider = \"gitlab\"\n\tGitProviderStash  GitProvider = \"stash\"\n)\n\n// Config defines the configuration for connecting to a GitProvider.\ntype Config struct {\n\t// Provider defines the GitProvider.\n\tProvider GitProvider\n\n\t// Hostname is the HTTP/S hostname of the Provider,\n\t// e.g. github.example.com.\n\tHostname string\n\n\t// Username contains the username used to authenticate with\n\t// the Provider.\n\tUsername string\n\n\t// Token contains the token used to authenticate with the\n\t// Provider.\n\tToken string\n\n\t// CABunle contains the CA bundle to use for the client.\n\tCaBundle []byte\n}\n"
  },
  {
    "path": "pkg/log/log.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage log\n\ntype Logger interface {\n\t// Actionf logs a formatted action message.\n\tActionf(format string, a ...interface{})\n\t// Generatef logs a formatted generate message.\n\tGeneratef(format string, a ...interface{})\n\t// Waitingf logs a formatted waiting message.\n\tWaitingf(format string, a ...interface{})\n\t// Successf logs a formatted success message.\n\tSuccessf(format string, a ...interface{})\n\t// Warningf logs a formatted warning message.\n\tWarningf(format string, a ...interface{})\n\t// Failuref logs a formatted failure message.\n\tFailuref(format string, a ...interface{})\n}\n"
  },
  {
    "path": "pkg/log/nop.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage log\n\ntype NopLogger struct{}\n\nfunc (NopLogger) Actionf(format string, a ...interface{}) {}\n\nfunc (NopLogger) Generatef(format string, a ...interface{}) {}\n\nfunc (NopLogger) Waitingf(format string, a ...interface{}) {}\n\nfunc (NopLogger) Successf(format string, a ...interface{}) {}\n\nfunc (NopLogger) Warningf(format string, a ...interface{}) {}\n\nfunc (NopLogger) Failuref(format string, a ...interface{}) {}\n"
  },
  {
    "path": "pkg/manifestgen/doc.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package manifestgen generates Kubernetes manifests for flux install\n// and the Git source and Kustomization manifests for flux bootstrap.\npackage manifestgen\n"
  },
  {
    "path": "pkg/manifestgen/install/install.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage install\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\tsecurejoin \"github.com/cyphar/filepath-securejoin\"\n\t\"github.com/hashicorp/go-cleanhttp\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n)\n\n// Generate returns the install manifests as a multi-doc YAML.\n// The manifests are built from a GitHub release or from a\n// Kustomize overlay if the supplied Options.BaseURL is a local path.\n// The manifestsBase should be set to an empty string when Generate is\n// called by consumers that don't embed the manifests.\nfunc Generate(options Options, manifestsBase string) (*manifestgen.Manifest, error) {\n\tctx, cancel := context.WithTimeout(context.Background(), options.Timeout)\n\tdefer cancel()\n\n\tvar err error\n\n\toutput, err := securejoin.SecureJoin(manifestsBase, options.ManifestFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !strings.HasPrefix(options.BaseURL, \"http\") {\n\t\tif err := build(options.BaseURL, output); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\t// download the manifests base from GitHub\n\t\tif manifestsBase == \"\" {\n\t\t\tmanifestsBase, err = manifestgen.MkdirTempAbs(\"\", options.Namespace)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"temp dir error: %w\", err)\n\t\t\t}\n\t\t\tdefer os.RemoveAll(manifestsBase)\n\t\t\toutput, err = securejoin.SecureJoin(manifestsBase, options.ManifestFile)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif err := fetch(ctx, options.BaseURL, options.Version, manifestsBase); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\tif err := generate(manifestsBase, options); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif err := build(manifestsBase, output); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tcontent, err := os.ReadFile(output)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &manifestgen.Manifest{\n\t\tPath:    path.Join(options.TargetPath, options.Namespace, options.ManifestFile),\n\t\tContent: fmt.Sprintf(\"%s\\n%s\", GetGenWarning(options), string(content)),\n\t}, nil\n}\n\n// GetLatestVersion calls the GitHub API and returns the latest released version.\nfunc GetLatestVersion() (string, error) {\n\tghURL := \"https://api.github.com/repos/fluxcd/flux2/releases/latest\"\n\tc := cleanhttp.DefaultClient()\n\tc.Timeout = 15 * time.Second\n\n\tres, err := c.Get(ghURL)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"GitHub API call failed: %w\", err)\n\t}\n\n\tif res.Body != nil {\n\t\tdefer res.Body.Close()\n\t}\n\n\ttype meta struct {\n\t\tTag string `json:\"tag_name\"`\n\t}\n\tvar m meta\n\tif err := json.NewDecoder(res.Body).Decode(&m); err != nil {\n\t\treturn \"\", fmt.Errorf(\"decoding GitHub API response failed: %w\", err)\n\t}\n\n\treturn m.Tag, err\n}\n\n// ExistingVersion calls the GitHub API to confirm the given version does exist.\nfunc ExistingVersion(version string) (bool, error) {\n\tif !strings.HasPrefix(version, \"v\") {\n\t\tversion = \"v\" + version\n\t}\n\n\tghURL := fmt.Sprintf(\"https://api.github.com/repos/fluxcd/flux2/releases/tags/%s\", version)\n\tc := cleanhttp.DefaultClient()\n\tc.Timeout = 15 * time.Second\n\n\tres, err := c.Get(ghURL)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"GitHub API call failed: %w\", err)\n\t}\n\n\tif res.Body != nil {\n\t\tdefer res.Body.Close()\n\t}\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusNotFound:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"GitHub API returned an unexpected status code (%d)\", res.StatusCode)\n\t}\n}\n\n// GetGenWarning generates a consistent generation warning in the install and bootstrap case in the following format:\n// # This manifest was generated by flux. DO NOT EDIT.\n// # Flux version: v0.21.1\n// # Components: source-controller,kustomize-controller,helm-controller,notification-controller,image-reflector-controller,image-automation-controller\nfunc GetGenWarning(options Options) string {\n\treturn fmt.Sprintf(\"---\\n%s\\n# Flux Version: %s\\n# Components: %s\", manifestgen.GenWarning, options.Version, strings.Join(options.Components, \",\"))\n}\n"
  },
  {
    "path": "pkg/manifestgen/install/install_test.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage install\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestGenerate(t *testing.T) {\n\topts := MakeDefaultOptions()\n\topts.TolerationKeys = []string{\"node.kubernetes.io/controllers\"}\n\toutput, err := Generate(opts, \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, component := range opts.Components {\n\t\timg := fmt.Sprintf(\"%s/%s\", opts.Registry, component)\n\t\tif !strings.Contains(output.Content, img) {\n\t\t\tt.Errorf(\"component image '%s' not found\", img)\n\t\t}\n\t}\n\n\tif !strings.Contains(output.Content, opts.TolerationKeys[0]) {\n\t\tt.Errorf(\"toleration key '%s' not found\", opts.TolerationKeys[0])\n\t}\n\n\twarning := GetGenWarning(opts)\n\tif !strings.HasPrefix(output.Content, warning) {\n\t\tt.Errorf(\"Generation warning '%s' not found\", warning)\n\t}\n\n\tt.Log(output)\n}\n"
  },
  {
    "path": "pkg/manifestgen/install/manifests.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage install\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/hashicorp/go-cleanhttp\"\n\n\t\"github.com/fluxcd/pkg/kustomize/filesys\"\n\t\"github.com/fluxcd/pkg/tar\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen/kustomization\"\n)\n\nfunc fetch(ctx context.Context, url, version, dir string) error {\n\tghURL := fmt.Sprintf(\"%s/latest/download/manifests.tar.gz\", url)\n\tif strings.HasPrefix(version, \"v\") {\n\t\tghURL = fmt.Sprintf(\"%s/download/%s/manifests.tar.gz\", url, version)\n\t}\n\n\treq, err := http.NewRequest(\"GET\", ghURL, nil)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create HTTP request for %s, error: %w\", ghURL, err)\n\t}\n\n\t// download\n\tresp, err := cleanhttp.DefaultClient().Do(req.WithContext(ctx))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to download manifests.tar.gz from %s, error: %w\", ghURL, err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// check response\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"failed to download manifests.tar.gz from %s, status: %s\", ghURL, resp.Status)\n\t}\n\n\t// extract\n\tif err = tar.Untar(resp.Body, dir, tar.WithMaxUntarSize(-1)); err != nil {\n\t\treturn fmt.Errorf(\"failed to untar manifests.tar.gz from %s, error: %w\", ghURL, err)\n\t}\n\n\treturn nil\n}\n\nfunc generate(base string, options Options) error {\n\tif containsItemString(options.Components, options.NotificationController) {\n\t\t// We need to use full domain name here, as some users may deploy flux\n\t\t// in environments that use http proxy.\n\t\t//\n\t\t// In such environments they normally add `.cluster.local` and `.local`\n\t\t// suffixes to `no_proxy` variable in order to prevent cluster-local\n\t\t// traffic from going through http proxy. Without fully specified\n\t\t// domain they need to mention `notifications-controller` explicitly in\n\t\t// `no_proxy` variable after debugging http proxy logs.\n\t\toptions.EventsAddr = fmt.Sprintf(\"http://%s.$(RUNTIME_NAMESPACE).svc.%s./\", options.NotificationController, options.ClusterDomain)\n\t}\n\n\tif err := execTemplate(options, namespaceTmpl, path.Join(base, \"namespace.yaml\")); err != nil {\n\t\treturn fmt.Errorf(\"generate namespace failed: %w\", err)\n\t}\n\n\tif err := execTemplate(options, labelsTmpl, path.Join(base, \"labels.yaml\")); err != nil {\n\t\treturn fmt.Errorf(\"generate labels failed: %w\", err)\n\t}\n\n\tif err := execTemplate(options, nodeSelectorTmpl, path.Join(base, \"node-selector.yaml\")); err != nil {\n\t\treturn fmt.Errorf(\"generate node selector failed: %w\", err)\n\t}\n\n\tif err := execTemplate(options, kustomizationTmpl, path.Join(base, \"kustomization.yaml\")); err != nil {\n\t\treturn fmt.Errorf(\"generate kustomization failed: %w\", err)\n\t}\n\n\tif err := os.MkdirAll(path.Join(base, \"roles\"), os.ModePerm); err != nil {\n\t\treturn fmt.Errorf(\"generate roles failed: %w\", err)\n\t}\n\n\tif err := execTemplate(options, kustomizationRolesTmpl, path.Join(base, \"roles/kustomization.yaml\")); err != nil {\n\t\treturn fmt.Errorf(\"generate roles kustomization failed: %w\", err)\n\t}\n\n\trbacFile := filepath.Join(base, \"roles/rbac.yaml\")\n\tif err := copyFile(filepath.Join(base, \"rbac.yaml\"), rbacFile); err != nil {\n\t\treturn fmt.Errorf(\"generate rbac failed: %w\", err)\n\t}\n\n\t// workaround for kustomize not being able to patch the SA in ClusterRoleBindings\n\tdefaultNS := MakeDefaultOptions().Namespace\n\tif defaultNS != options.Namespace {\n\t\trbac, err := os.ReadFile(rbacFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"reading rbac file failed: %w\", err)\n\t\t}\n\t\trbac = bytes.ReplaceAll(rbac, []byte(defaultNS), []byte(options.Namespace))\n\t\tif err := os.WriteFile(rbacFile, rbac, os.ModePerm); err != nil {\n\t\t\treturn fmt.Errorf(\"replacing service account namespace in rbac failed: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc build(base, output string) error {\n\tresources, err := kustomization.Build(base)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toutputBase := filepath.Dir(strings.TrimSuffix(output, string(filepath.Separator)))\n\tfs, err := filesys.MakeFsOnDiskSecure(outputBase)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err = fs.WriteFile(output, resources); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/manifestgen/install/options.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage install\n\nimport \"time\"\n\ntype Options struct {\n\tBaseURL                string\n\tVersion                string\n\tNamespace              string\n\tComponents             []string\n\tComponentsExtra        []string\n\tEventsAddr             string\n\tRegistry               string\n\tRegistryCredential     string\n\tImagePullSecret        string\n\tWatchAllNamespaces     bool\n\tNetworkPolicy          bool\n\tLogLevel               string\n\tNotificationController string\n\tManifestFile           string\n\tTimeout                time.Duration\n\tTargetPath             string\n\tClusterDomain          string\n\tTolerationKeys         []string\n}\n\nfunc MakeDefaultOptions() Options {\n\treturn Options{\n\t\tVersion:                \"latest\",\n\t\tNamespace:              \"flux-system\",\n\t\tComponents:             []string{\"source-controller\", \"kustomize-controller\", \"helm-controller\", \"notification-controller\"},\n\t\tComponentsExtra:        []string{\"image-reflector-controller\", \"image-automation-controller\", \"source-watcher\"},\n\t\tEventsAddr:             \"\",\n\t\tRegistry:               \"ghcr.io/fluxcd\",\n\t\tRegistryCredential:     \"\",\n\t\tImagePullSecret:        \"\",\n\t\tWatchAllNamespaces:     true,\n\t\tNetworkPolicy:          true,\n\t\tLogLevel:               \"info\",\n\t\tBaseURL:                \"https://github.com/fluxcd/flux2/releases\",\n\t\tNotificationController: \"notification-controller\",\n\t\tManifestFile:           \"gotk-components.yaml\",\n\t\tTimeout:                time.Minute,\n\t\tTargetPath:             \"\",\n\t\tClusterDomain:          \"cluster.local\",\n\t}\n}\n\nfunc containsItemString(s []string, e string) bool {\n\tfor _, a := range s {\n\t\tif a == e {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/manifestgen/install/templates.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage install\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"text/template\"\n)\n\nvar kustomizationTmpl = `---\n{{- $eventsAddr := .EventsAddr }}\n{{- $watchAllNamespaces := .WatchAllNamespaces }}\n{{- $registry := .Registry }}\n{{- $logLevel := .LogLevel }}\n{{- $clusterDomain := .ClusterDomain }}\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nnamespace: {{.Namespace}}\n\ntransformers:\n  - labels.yaml\n\nresources:\n  - namespace.yaml\n{{- if .NetworkPolicy }}\n  - policies.yaml\n{{- end }}\n  - roles\n{{- range .Components }}\n  - {{.}}.yaml\n{{- end }}\n\npatches:\n- path: node-selector.yaml\n  target:\n    kind: Deployment\n{{- range $i, $component := .Components }}\n{{- if eq $component \"notification-controller\" }}\n- target:\n    group: apps\n    version: v1\n    kind: Deployment\n    name: {{$component}}\n  patch: |-\n    - op: replace\n      path: /spec/template/spec/containers/0/args/0\n      value: --watch-all-namespaces={{$watchAllNamespaces}}\n    - op: replace\n      path: /spec/template/spec/containers/0/args/1\n      value: --log-level={{$logLevel}}\n{{- else if or (eq $component \"source-controller\") (eq $component \"source-watcher\") }}\n- target:\n    group: apps\n    version: v1\n    kind: Deployment\n    name: {{$component}}\n  patch: |-\n    - op: replace\n      path: /spec/template/spec/containers/0/args/0\n      value: --events-addr={{$eventsAddr}}\n    - op: replace\n      path: /spec/template/spec/containers/0/args/1\n      value: --watch-all-namespaces={{$watchAllNamespaces}}\n    - op: replace\n      path: /spec/template/spec/containers/0/args/2\n      value: --log-level={{$logLevel}}\n    - op: replace\n      path: /spec/template/spec/containers/0/args/6\n      value: --storage-adv-addr={{$component}}.$(RUNTIME_NAMESPACE).svc.{{$clusterDomain}}.\n{{- else }}\n- target:\n    group: apps\n    version: v1\n    kind: Deployment\n    name: {{$component}}\n  patch: |-\n    - op: replace\n      path: /spec/template/spec/containers/0/args/0\n      value: --events-addr={{$eventsAddr}}\n    - op: replace\n      path: /spec/template/spec/containers/0/args/1\n      value: --watch-all-namespaces={{$watchAllNamespaces}}\n    - op: replace\n      path: /spec/template/spec/containers/0/args/2\n      value: --log-level={{$logLevel}}\n{{- end }}\n{{- end }}\n{{- range $i, $component := .Components }}\n{{- if eq $component \"source-watcher\" }}\n- target:\n    kind: Deployment\n    name: \"(kustomize-controller|helm-controller)\"\n  patch: |-\n    - op: add\n      path: /spec/template/spec/containers/0/args/-\n      value: --feature-gates=ExternalArtifact=true\n{{- end }}\n{{- end }}\n{{- if $registry }}\nimages:\n{{- range $i, $component := .Components }}\n  - name: fluxcd/{{$component}}\n    newName: {{$registry}}/{{$component}}\n{{- end }}\n{{- end }}\n`\n\nvar kustomizationRolesTmpl = `---\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nnamespace: {{.Namespace}}\nresources:\n  - rbac.yaml\nnameSuffix: -{{.Namespace}}\n`\n\nvar nodeSelectorTmpl = `---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: all\nspec:\n  template:\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n{{- if .ImagePullSecret }}\n      imagePullSecrets:\n       - name: {{.ImagePullSecret}}\n{{- end }}\n{{ if gt (len .TolerationKeys) 0 }}\n      tolerations:\n{{- range $i, $key := .TolerationKeys }}\n       - key: \"{{$key}}\"\n         operator: \"Exists\"\n{{- end }}\n{{- end }}\n`\n\nvar labelsTmpl = `---\napiVersion: builtin\nkind: LabelTransformer\nmetadata:\n  name: labels\nlabels:\n  app.kubernetes.io/instance: {{.Namespace}}\n  app.kubernetes.io/version: \"{{.Version}}\"\n  app.kubernetes.io/part-of: flux\nfieldSpecs:\n  - path: metadata/labels\n    create: true\n  - kind: Deployment\n    path: spec/template/metadata/labels\n    create: true\n`\n\nvar namespaceTmpl = `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{.Namespace}}\n  labels:\n    pod-security.kubernetes.io/warn: restricted\n    pod-security.kubernetes.io/warn-version: latest\n`\n\nfunc execTemplate(obj interface{}, tmpl, filename string) error {\n\tt, err := template.New(\"tmpl\").Parse(tmpl)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar data bytes.Buffer\n\twriter := bufio.NewWriter(&data)\n\tif err := t.Execute(writer, obj); err != nil {\n\t\treturn err\n\t}\n\n\tif err := writer.Flush(); err != nil {\n\t\treturn err\n\t}\n\n\tfile, err := os.Create(filename)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\n\t_, err = io.WriteString(file, data.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn file.Sync()\n}\n\nfunc copyFile(src, dst string) error {\n\tin, err := os.Open(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer in.Close()\n\n\tout, err := os.Create(dst)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer out.Close()\n\n\t_, err = io.Copy(out, in)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn out.Close()\n}\n"
  },
  {
    "path": "pkg/manifestgen/kustomization/kustomization.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kustomization\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"sigs.k8s.io/kustomize/api/konfig\"\n\t\"sigs.k8s.io/kustomize/api/krusty\"\n\t\"sigs.k8s.io/kustomize/api/provider\"\n\tkustypes \"sigs.k8s.io/kustomize/api/types\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/pkg/kustomize/filesys\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n)\n\n// Generate scans the given directory for Kubernetes manifests and creates a\n// konfig.DefaultKustomizationFileName file, including all discovered manifests\n// as resources.\nfunc Generate(options Options) (*manifestgen.Manifest, error) {\n\tkfile := filepath.Join(options.TargetPath, konfig.DefaultKustomizationFileName())\n\tabskfile := filepath.Join(options.BaseDir, kfile)\n\n\tscan := func(base string) ([]string, error) {\n\t\tvar paths []string\n\t\tpvd := provider.NewDefaultDepProvider()\n\t\trf := pvd.GetResourceFactory()\n\t\terr := options.FileSystem.Walk(base, func(path string, info os.FileInfo, err error) error {\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif path == base {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif info.IsDir() {\n\t\t\t\t// If a sub-directory contains an existing Kustomization file, add the\n\t\t\t\t// directory as a resource and do not decent into it.\n\t\t\t\tfor _, kfilename := range konfig.RecognizedKustomizationFileNames() {\n\t\t\t\t\tif options.FileSystem.Exists(filepath.Join(path, kfilename)) {\n\t\t\t\t\t\tpaths = append(paths, path)\n\t\t\t\t\t\treturn filepath.SkipDir\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tfContents, err := options.FileSystem.ReadFile(path)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err := rf.SliceFromBytes(fContents); err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tpaths = append(paths, path)\n\t\t\treturn nil\n\t\t})\n\t\treturn paths, err\n\t}\n\n\tif _, err := os.Stat(abskfile); err != nil {\n\t\tabs, err := filepath.Abs(filepath.Dir(abskfile))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfiles, err := scan(abs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tf, err := options.FileSystem.Create(abskfile)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err = f.Close(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tkus := kustypes.Kustomization{\n\t\t\tTypeMeta: kustypes.TypeMeta{\n\t\t\t\tAPIVersion: kustypes.KustomizationVersion,\n\t\t\t\tKind:       kustypes.KustomizationKind,\n\t\t\t},\n\t\t}\n\n\t\tvar resources []string\n\t\tfor _, file := range files {\n\t\t\trelP, err := filepath.Rel(abs, file)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tresources = append(resources, relP)\n\t\t}\n\n\t\tkus.Resources = resources\n\t\tkd, err := yaml.Marshal(kus)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn &manifestgen.Manifest{\n\t\t\tPath:    kfile,\n\t\t\tContent: string(kd),\n\t\t}, nil\n\t}\n\n\tkd, err := os.ReadFile(abskfile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &manifestgen.Manifest{\n\t\tPath:    kfile,\n\t\tContent: string(kd),\n\t}, nil\n}\n\n// kustomizeBuildMutex is a workaround for a concurrent map read and map write bug.\n// TODO(stefan): https://github.com/kubernetes-sigs/kustomize/issues/3659\nvar kustomizeBuildMutex sync.Mutex\n\n// Build takes the path to a directory with a konfig.RecognizedKustomizationFileNames,\n// builds it, and returns the resulting manifests as multi-doc YAML. It restricts the\n// Kustomize file system to the parent directory of the base.\nfunc Build(base string) ([]byte, error) {\n\t// TODO(hidde): drop this when consumers have moved away to BuildWithRoot.\n\tparent := filepath.Dir(strings.TrimSuffix(base, string(filepath.Separator)))\n\treturn BuildWithRoot(parent, base)\n}\n\n// BuildWithRoot takes the path to a directory with a konfig.RecognizedKustomizationFileNames,\n// builds it, and returns the resulting manifests as multi-doc YAML.\n// The Kustomize file system is restricted to root.\nfunc BuildWithRoot(root, base string) ([]byte, error) {\n\tkustomizeBuildMutex.Lock()\n\tdefer kustomizeBuildMutex.Unlock()\n\n\tfs, err := filesys.MakeFsOnDiskSecureBuild(root)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar kfile string\n\tfor _, f := range konfig.RecognizedKustomizationFileNames() {\n\t\tif kf := filepath.Join(base, f); fs.Exists(kf) {\n\t\t\tkfile = kf\n\t\t\tbreak\n\t\t}\n\t}\n\tif kfile == \"\" {\n\t\treturn nil, fmt.Errorf(\"%s not found\", konfig.DefaultKustomizationFileName())\n\t}\n\n\t// Convert absolute paths to relative when possible, for kustomize\n\t// compatibility. If filepath.Rel fails (e.g. paths on different\n\t// Windows drives), keep the absolute path — kustomize handles\n\t// absolute paths correctly since go-getter was removed.\n\t// See: https://github.com/kubernetes-sigs/kustomize/issues/2789\n\t//      https://github.com/fluxcd/flux2/issues/1153\n\tif filepath.IsAbs(base) {\n\t\twd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif relBase, err := filepath.Rel(wd, base); err == nil {\n\t\t\tbase = relBase\n\t\t}\n\t}\n\n\tbuildOptions := &krusty.Options{\n\t\tLoadRestrictions: kustypes.LoadRestrictionsNone,\n\t\tPluginConfig:     kustypes.DisabledPluginConfig(),\n\t}\n\n\tk := krusty.MakeKustomizer(buildOptions)\n\tm, err := k.Run(fs, base)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources, err := m.AsYaml()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn resources, nil\n}\n"
  },
  {
    "path": "pkg/manifestgen/kustomization/options.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kustomization\n\nimport \"sigs.k8s.io/kustomize/kyaml/filesys\"\n\ntype Options struct {\n\tFileSystem filesys.FileSystem\n\tBaseDir    string\n\tTargetPath string\n}\n\nfunc MakeDefaultOptions() Options {\n\t// TODO(hidde): switch MakeFsOnDisk to MakeFsOnDiskSecureBuild when we\n\t//  break API.\n\treturn Options{\n\t\tFileSystem: filesys.MakeFsOnDisk(),\n\t\tBaseDir:    \"\",\n\t\tTargetPath: \"\",\n\t}\n}\n"
  },
  {
    "path": "pkg/manifestgen/labels.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage manifestgen\n\n// These labels can be used to track down the namespace, custom resource definitions, deployments,\n// services, network policies, service accounts, cluster roles and cluster role bindings belonging to Flux.\nconst (\n\tPartOfLabelKey   = \"app.kubernetes.io/part-of\"\n\tPartOfLabelValue = \"flux\"\n\tInstanceLabelKey = \"app.kubernetes.io/instance\"\n\tVersionLabelKey  = \"app.kubernetes.io/version\"\n)\n"
  },
  {
    "path": "pkg/manifestgen/manifest.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage manifestgen\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\tsecurejoin \"github.com/cyphar/filepath-securejoin\"\n)\n\nconst GenWarning = \"# This manifest was generated by flux. DO NOT EDIT.\"\n\n// Manifest holds the data of a multi-doc YAML\ntype Manifest struct {\n\t// Relative path to the YAML file\n\tPath string\n\t// Content in YAML format\n\tContent string\n}\n\n// WriteFile writes the YAML content to a file inside the root path.\n// If the file does not exist, WriteFile creates it with permissions perm,\n// otherwise WriteFile overwrites the file, without changing permissions.\nfunc (m *Manifest) WriteFile(rootDir string) (string, error) {\n\toutput, err := securejoin.SecureJoin(rootDir, m.Path)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif err := os.MkdirAll(filepath.Dir(output), os.ModePerm); err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to create dir, error: %w\", err)\n\t}\n\n\tif err := os.WriteFile(output, []byte(m.Content), os.ModePerm); err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to write file, error: %w\", err)\n\t}\n\treturn output, nil\n}\n"
  },
  {
    "path": "pkg/manifestgen/sourcesecret/options.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sourcesecret\n\nimport (\n\t\"crypto/elliptic\"\n\n\t\"github.com/fluxcd/pkg/ssh\"\n)\n\ntype PrivateKeyAlgorithm string\n\nconst (\n\tRSAPrivateKeyAlgorithm     PrivateKeyAlgorithm = \"rsa\"\n\tECDSAPrivateKeyAlgorithm   PrivateKeyAlgorithm = \"ecdsa\"\n\tEd25519PrivateKeyAlgorithm PrivateKeyAlgorithm = \"ed25519\"\n)\n\nconst (\n\tAddressSecretKey    = \"address\"\n\tUsernameSecretKey   = \"username\"\n\tPasswordSecretKey   = \"password\"\n\tCACrtSecretKey      = \"ca.crt\"\n\tTLSCrtSecretKey     = \"tls.crt\"\n\tTLSKeySecretKey     = \"tls.key\"\n\tPrivateKeySecretKey = \"identity\"\n\tPublicKeySecretKey  = \"identity.pub\"\n\tKnownHostsSecretKey = \"known_hosts\"\n\tBearerTokenKey      = \"bearerToken\"\n\tTrustPolicyKey      = \"trustpolicy.json\"\n\n\t// Deprecated: Replaced by CACrtSecretKey, but kept for backwards\n\t// compatibility with deprecated TLS flags.\n\tCAFileSecretKey = \"caFile\"\n\t// Deprecated: Replaced by TLSCrtSecretKey, but kept for backwards\n\t// compatibility with deprecated TLS flags.\n\tCertFileSecretKey = \"certFile\"\n\t// Deprecated: Replaced by TLSKeySecretKey, but kept for backwards\n\t// compatibility with deprecated TLS flags.\n\tKeyFileSecretKey = \"keyFile\"\n)\n\ntype Options struct {\n\tName                string\n\tNamespace           string\n\tLabels              map[string]string\n\tRegistry            string\n\tSSHHostname         string\n\tPrivateKeyAlgorithm PrivateKeyAlgorithm\n\tRSAKeyBits          int\n\tECDSACurve          elliptic.Curve\n\tKeypair             *ssh.KeyPair\n\tUsername            string\n\tPassword            string\n\tCACrt               []byte\n\tTLSCrt              []byte\n\tTLSKey              []byte\n\tTargetPath          string\n\tManifestFile        string\n\tBearerToken         string\n\tVerificationCrts    []VerificationCrt\n\tTrustPolicy         []byte\n\tAddress             string\n\n\t// GitHub App options\n\tGitHubAppID                string\n\tGitHubAppInstallationOwner string\n\tGitHubAppInstallationID    string\n\tGitHubAppPrivateKey        string\n\tGitHubAppBaseURL           string\n}\n\ntype VerificationCrt struct {\n\tName  string\n\tCACrt []byte\n}\n\nfunc MakeDefaultOptions() Options {\n\treturn Options{\n\t\tName:                \"flux-system\",\n\t\tNamespace:           \"flux-system\",\n\t\tLabels:              map[string]string{},\n\t\tPrivateKeyAlgorithm: RSAPrivateKeyAlgorithm,\n\t\tUsername:            \"\",\n\t\tPassword:            \"\",\n\t\tManifestFile:        \"secret.yaml\",\n\t\tBearerToken:         \"\",\n\t}\n}\n"
  },
  {
    "path": "pkg/manifestgen/sourcesecret/sourcesecret.go",
    "content": "/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sourcesecret\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path\"\n\t\"time\"\n\n\tcryptssh \"golang.org/x/crypto/ssh\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/fluxcd/pkg/runtime/secrets\"\n\t\"github.com/fluxcd/pkg/ssh\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n)\n\nconst defaultSSHPort = 22\n\n// types gotten from https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/create/create_secret_docker.go#L64-L84\n\n// DockerConfigJSON represents a local docker auth config file\n// for pulling images.\ntype DockerConfigJSON struct {\n\tAuths DockerConfig `json:\"auths\"`\n}\n\n// DockerConfig represents the config file used by the docker CLI.\n// This config that represents the credentials that should be used\n// when pulling images from specific image repositories.\ntype DockerConfig map[string]DockerConfigEntry\n\n// DockerConfigEntry holds the user information that grant the access to docker registry\ntype DockerConfigEntry struct {\n\tUsername string `json:\"username,omitempty\"`\n\tPassword string `json:\"password,omitempty\"`\n\tEmail    string `json:\"email,omitempty\"`\n\tAuth     string `json:\"auth,omitempty\"`\n}\n\nfunc GenerateGit(options Options) (*manifestgen.Manifest, error) {\n\tvar err error\n\n\tvar keypair *ssh.KeyPair\n\tswitch {\n\tcase options.Username != \"\" && options.Password != \"\":\n\t\t// noop\n\tcase options.Keypair != nil:\n\t\tkeypair = options.Keypair\n\tcase len(options.PrivateKeyAlgorithm) > 0:\n\t\tif keypair, err = generateKeyPair(options); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar hostKey []byte\n\tif keypair != nil {\n\t\tif hostKey, err = ScanHostKey(options.SSHHostname); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsecret := buildGitSecret(keypair, hostKey, options)\n\treturn secretToManifest(&secret, options)\n}\n\nfunc GenerateTLS(options Options) (*manifestgen.Manifest, error) {\n\tvar opts []secrets.TLSSecretOption\n\n\tif len(options.TLSCrt) > 0 || len(options.TLSKey) > 0 {\n\t\topts = append(opts, secrets.WithCertKeyPair(options.TLSCrt, options.TLSKey))\n\t}\n\tif len(options.CACrt) > 0 {\n\t\topts = append(opts, secrets.WithCAData(options.CACrt))\n\t}\n\n\tsecret, err := secrets.MakeTLSSecret(options.Name, options.Namespace, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsecret.Labels = options.Labels\n\treturn secretToManifest(secret, options)\n}\n\nfunc GenerateOCI(options Options) (*manifestgen.Manifest, error) {\n\tsecret, err := secrets.MakeRegistrySecret(\n\t\toptions.Name,\n\t\toptions.Namespace,\n\t\toptions.Registry,\n\t\toptions.Username,\n\t\toptions.Password,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsecret.Labels = options.Labels\n\treturn secretToManifest(secret, options)\n}\n\nfunc GenerateHelm(options Options) (*manifestgen.Manifest, error) {\n\thasBasicAuth := options.Username != \"\" || options.Password != \"\"\n\thasClientCert := len(options.TLSCrt) > 0 || len(options.TLSKey) > 0\n\thasCACert := len(options.CACrt) > 0\n\n\tvar secret *corev1.Secret\n\tvar err error\n\n\tswitch {\n\tcase hasClientCert:\n\t\t// Priority 1: Client certificate (mTLS) - highest priority like CertSecretRef\n\t\tvar opts []secrets.TLSSecretOption\n\t\topts = append(opts, secrets.WithCertKeyPair(options.TLSCrt, options.TLSKey))\n\t\tif hasCACert {\n\t\t\topts = append(opts, secrets.WithCAData(options.CACrt))\n\t\t}\n\n\t\tsecret, err = secrets.MakeTLSSecret(options.Name, options.Namespace, opts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\tcase hasBasicAuth:\n\t\t// Priority 2: Basic authentication (can include CA certificate)\n\t\tsecret, err = secrets.MakeBasicAuthSecret(\n\t\t\toptions.Name,\n\t\t\toptions.Namespace,\n\t\t\toptions.Username,\n\t\t\toptions.Password,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Add CA certificate to BasicAuth secret for HTTPS repositories with custom CA\n\t\t// (e.g., self-signed certificates or internal certificate authorities)\n\t\tif hasCACert {\n\t\t\tif secret.StringData == nil {\n\t\t\t\tsecret.StringData = make(map[string]string)\n\t\t\t}\n\t\t\tsecret.StringData[CACrtSecretKey] = string(options.CACrt)\n\t\t}\n\n\tcase hasCACert:\n\t\t// Priority 3: CA certificate only\n\t\tvar opts []secrets.TLSSecretOption\n\t\topts = append(opts, secrets.WithCAData(options.CACrt))\n\n\t\tsecret, err = secrets.MakeTLSSecret(options.Name, options.Namespace, opts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\tdefault:\n\t\t// No authentication credentials provided - create empty secret for backward compatibility\n\t\tsecret = &corev1.Secret{\n\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\tKind:       \"Secret\",\n\t\t\t},\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      options.Name,\n\t\t\t\tNamespace: options.Namespace,\n\t\t\t},\n\t\t\tStringData: map[string]string{},\n\t\t}\n\t}\n\n\tsecret.Labels = options.Labels\n\treturn secretToManifest(secret, options)\n}\n\nfunc GenerateProxy(options Options) (*manifestgen.Manifest, error) {\n\tsecret, err := secrets.MakeProxySecret(\n\t\toptions.Name,\n\t\toptions.Namespace,\n\t\toptions.Address,\n\t\toptions.Username,\n\t\toptions.Password,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsecret.Labels = options.Labels\n\treturn secretToManifest(secret, options)\n}\n\nfunc GenerateNotation(options Options) (*manifestgen.Manifest, error) {\n\tsecret := &corev1.Secret{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tAPIVersion: \"v1\",\n\t\t\tKind:       \"Secret\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      options.Name,\n\t\t\tNamespace: options.Namespace,\n\t\t\tLabels:    options.Labels,\n\t\t},\n\t\tStringData: map[string]string{},\n\t}\n\n\tfor _, crt := range options.VerificationCrts {\n\t\tsecret.StringData[crt.Name] = string(crt.CACrt)\n\t}\n\n\tif len(options.TrustPolicy) > 0 {\n\t\tsecret.StringData[TrustPolicyKey] = string(options.TrustPolicy)\n\t}\n\n\treturn secretToManifest(secret, options)\n}\n\nfunc GenerateGitHubApp(options Options) (*manifestgen.Manifest, error) {\n\tvar opts []secrets.GitHubAppOption\n\tif owner := options.GitHubAppInstallationOwner; owner != \"\" {\n\t\topts = append(opts, secrets.WithGitHubAppInstallationOwner(owner))\n\t}\n\tif id := options.GitHubAppInstallationID; id != \"\" {\n\t\topts = append(opts, secrets.WithGitHubAppInstallationID(id))\n\t}\n\tif u := options.GitHubAppBaseURL; u != \"\" {\n\t\topts = append(opts, secrets.WithGitHubAppBaseURL(u))\n\t}\n\tsecret, err := secrets.MakeGitHubAppSecret(\n\t\toptions.Name,\n\t\toptions.Namespace,\n\t\toptions.GitHubAppID,\n\t\toptions.GitHubAppPrivateKey,\n\t\topts...,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsecret.Labels = options.Labels\n\treturn secretToManifest(secret, options)\n}\n\nfunc LoadKeyPairFromPath(path, password string) (*ssh.KeyPair, error) {\n\tif path == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tb, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to open private key file: %w\", err)\n\t}\n\treturn LoadKeyPair(b, password)\n}\n\nfunc LoadKeyPair(privateKey []byte, password string) (*ssh.KeyPair, error) {\n\tvar ppk cryptssh.Signer\n\tvar err error\n\tif password != \"\" {\n\t\tppk, err = cryptssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(password))\n\t} else {\n\t\tppk, err = cryptssh.ParsePrivateKey(privateKey)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ssh.KeyPair{\n\t\tPublicKey:  cryptssh.MarshalAuthorizedKey(ppk.PublicKey()),\n\t\tPrivateKey: privateKey,\n\t}, nil\n}\n\nfunc buildGitSecret(keypair *ssh.KeyPair, hostKey []byte, options Options) (secret corev1.Secret) {\n\tsecret.TypeMeta = metav1.TypeMeta{\n\t\tAPIVersion: \"v1\",\n\t\tKind:       \"Secret\",\n\t}\n\tsecret.ObjectMeta = metav1.ObjectMeta{\n\t\tName:      options.Name,\n\t\tNamespace: options.Namespace,\n\t}\n\tsecret.Labels = options.Labels\n\tsecret.StringData = map[string]string{}\n\n\tif options.Username != \"\" && options.Password != \"\" {\n\t\tsecret.StringData[UsernameSecretKey] = options.Username\n\t\tsecret.StringData[PasswordSecretKey] = options.Password\n\t}\n\tif options.BearerToken != \"\" {\n\t\tsecret.StringData[BearerTokenKey] = options.BearerToken\n\t}\n\n\tif len(options.CACrt) != 0 {\n\t\tsecret.StringData[CACrtSecretKey] = string(options.CACrt)\n\t}\n\n\t// SSH keypair (identity + identity.pub + known_hosts)\n\tif keypair != nil && len(hostKey) != 0 {\n\t\tsecret.StringData[PrivateKeySecretKey] = string(keypair.PrivateKey)\n\t\tsecret.StringData[PublicKeySecretKey] = string(keypair.PublicKey)\n\t\tsecret.StringData[KnownHostsSecretKey] = string(hostKey)\n\t\t// set password if present\n\t\tif options.Password != \"\" {\n\t\t\tsecret.StringData[PasswordSecretKey] = string(options.Password)\n\t\t}\n\t}\n\n\treturn secret\n}\n\nfunc secretToManifest(secret *corev1.Secret, options Options) (*manifestgen.Manifest, error) {\n\tb, err := yaml.Marshal(secret)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &manifestgen.Manifest{\n\t\tPath:    path.Join(options.TargetPath, options.Namespace, options.ManifestFile),\n\t\tContent: fmt.Sprintf(\"---\\n%s\", resourceToString(b)),\n\t}, nil\n}\n\nfunc generateKeyPair(options Options) (*ssh.KeyPair, error) {\n\tvar keyGen ssh.KeyPairGenerator\n\tswitch options.PrivateKeyAlgorithm {\n\tcase RSAPrivateKeyAlgorithm:\n\t\tkeyGen = ssh.NewRSAGenerator(options.RSAKeyBits)\n\tcase ECDSAPrivateKeyAlgorithm:\n\t\tkeyGen = ssh.NewECDSAGenerator(options.ECDSACurve)\n\tcase Ed25519PrivateKeyAlgorithm:\n\t\tkeyGen = ssh.NewEd25519Generator()\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported public key algorithm: %s\", options.PrivateKeyAlgorithm)\n\t}\n\tpair, err := keyGen.Generate()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"key pair generation failed, error: %w\", err)\n\t}\n\treturn pair, nil\n}\n\nfunc ScanHostKey(host string) ([]byte, error) {\n\tif _, _, err := net.SplitHostPort(host); err != nil {\n\t\t// Assume we are dealing with a hostname without a port,\n\t\t// append the default SSH port as this is required for\n\t\t// host key scanning to work.\n\t\thost = fmt.Sprintf(\"%s:%d\", host, defaultSSHPort)\n\t}\n\thostKey, err := ssh.ScanHostKey(host, 30*time.Second, []string{}, false)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"SSH key scan for host %s failed, error: %w\", host, err)\n\t}\n\treturn bytes.TrimSpace(hostKey), nil\n}\n\nfunc resourceToString(data []byte) string {\n\tdata = bytes.Replace(data, []byte(\"  creationTimestamp: null\\n\"), []byte(\"\"), 1)\n\tdata = bytes.Replace(data, []byte(\"status: {}\\n\"), []byte(\"\"), 1)\n\treturn string(data)\n}\n\nfunc GenerateDockerConfigJson(url, username, password string) ([]byte, error) {\n\tcred := fmt.Sprintf(\"%s:%s\", username, password)\n\tauth := base64.StdEncoding.EncodeToString([]byte(cred))\n\tcfg := DockerConfigJSON{\n\t\tAuths: map[string]DockerConfigEntry{\n\t\t\turl: {\n\t\t\t\tUsername: username,\n\t\t\t\tPassword: password,\n\t\t\t\tAuth:     auth,\n\t\t\t},\n\t\t},\n\t}\n\n\treturn json.Marshal(cfg)\n}\n"
  },
  {
    "path": "pkg/manifestgen/sourcesecret/sourcesecret_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sourcesecret\n\nimport (\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"golang.org/x/crypto/ssh\"\n\t\"golang.org/x/crypto/ssh/testdata\"\n)\n\nfunc Test_passwordLoadKeyPair(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tprivateKeyPath string\n\t\tpublicKeyPath  string\n\t\tpassword       string\n\t}{\n\t\t{\n\t\t\tname:           \"private key pair with password\",\n\t\t\tprivateKeyPath: \"testdata/password_rsa\",\n\t\t\tpublicKeyPath:  \"testdata/password_rsa.pub\",\n\t\t\tpassword:       \"password\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tpk, _ := os.ReadFile(tt.privateKeyPath)\n\t\t\tppk, _ := os.ReadFile(tt.publicKeyPath)\n\n\t\t\tgot, err := LoadKeyPair(pk, tt.password)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"loadKeyPair() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(got.PrivateKey, pk) {\n\t\t\t\tt.Errorf(\"PrivateKey %s != %s\", got.PrivateKey, pk)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got.PublicKey, ppk) {\n\t\t\t\tt.Errorf(\"PublicKey %s != %s\", got.PublicKey, ppk)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_PasswordlessLoadKeyPair(t *testing.T) {\n\tfor algo, privateKey := range testdata.PEMBytes {\n\t\tt.Run(algo, func(t *testing.T) {\n\t\t\tgot, err := LoadKeyPair(privateKey, \"\")\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"loadKeyPair() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(got.PrivateKey, privateKey) {\n\t\t\t\tt.Errorf(\"PrivateKey %s != %s\", got.PrivateKey, string(privateKey))\n\t\t\t}\n\n\t\t\tsigner, err := ssh.ParsePrivateKey(privateKey)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(got.PublicKey, ssh.MarshalAuthorizedKey(signer.PublicKey())) {\n\t\t\t\tt.Errorf(\"PublicKey %s != %s\", got.PublicKey, ssh.MarshalAuthorizedKey(signer.PublicKey()))\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/manifestgen/sourcesecret/testdata/password_rsa",
    "content": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABB18lOcbN\nQ7pk768hyQUymCAAAAEAAAAAEAAACXAAAAB3NzaC1yc2EAAAADAQABAAAAgQCi/p5KtpRl\ntu3yxEsc+NtR9kRBuYhsh55609dNiTonLlL7K00pLCEWrtEIVM7FzD8IA6Q1NMNBFijje3\n6gnqMlo6M/cJObkGfZFC025diLI5/ND5R/l60XLtRZfh92K9nCsQKsJaW+R8LTtBdhil/q\nM/+u07sFHzzk6/36FStg/QAAAiAQAjBJ6TYZuu4iHrrQgbhmlQem18Xjm8f2M9M0BrYgJV\nF5OVL7mL/bsFTp0IA92HXnxGs0gF4ue5ujCE7SWOyr4SpJfgijExnmDPkZ9nhCG06MsCQ9\nuU7tTtEnrYcZ5/mmEe8E/O74Mo8xBqI8Unv95eF4p/tAMcDctVX39/lSjP1UZYL6vrLk2L\nSoEWK2DmZ2ZYdtBJ20AGDJ7MRIX3X/+qZkuYcy7GfPTuDKPIyrr7UEIYH//x8RaGuvOUuu\nP5bXGOBCTayZgGeWwNDePVITxMhpqTYjy7hqJ1ppBEv1svvbax5ksbwTRzU6quN75o+tbX\nhf5v0HbiRl3w6LtuwciiQtGsgijxt1noViZvpMLam5EJ3+eTnKnfEPxBMaCx7qepqPT9yI\nGJ/myyB/+FMkVe9epBeO2wyBTPPzr9O8co8SbcFkFEpcmxKk8toPf4F4XGs7lnibsDXGWE\ns2+WPmf17WLgpTMLutSVFIxw/V6ajnVrTTTE9AOwC9TYBx152YTWGILUlMMQ4AdNri8H80\nk/RMv18Tut6k4v6I1aKfUSfXRaaxwagt6zMJ2TvYJFsskfTboM/FjzcDvl4jWQvfcoKJJa\nda6L6TwnfL03qI+tdT84R4a9oYgZ27WB0ekPnmDpWgIl5jN/F2mHz2/KpuAzC7JgpPdzty\n271M16OmGvgerrvc/57d0bh4x/E9gj3ZMpkoXBH/lfs5c7LbvRxQ\n-----END OPENSSH PRIVATE KEY-----\n"
  },
  {
    "path": "pkg/manifestgen/sourcesecret/testdata/password_rsa.pub",
    "content": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCi/p5KtpRltu3yxEsc+NtR9kRBuYhsh55609dNiTonLlL7K00pLCEWrtEIVM7FzD8IA6Q1NMNBFijje36gnqMlo6M/cJObkGfZFC025diLI5/ND5R/l60XLtRZfh92K9nCsQKsJaW+R8LTtBdhil/qM/+u07sFHzzk6/36FStg/Q==\n"
  },
  {
    "path": "pkg/manifestgen/sourcesecret/testdata/rsa",
    "content": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAYEA3igXbgoTAydPOiEb4VfRPM4e26S16ZXhIEt95vka8wdcru97JKFl\n3yK6i0RALylrMAyjequXTNntZm2unngMuYE3OeFoY3dE1/xcWEVu8gE6AiGv74SzQsKtQv\nlqlXhF9s2cVyfJLmt5cvQP6zRxmx8KaZL6bN3PLDMIuq4Lit3BEn9KON89+uWKjXr9jU6/\nMlaQ4jn9VYMczhMDQpTyTMFgIBLuJYa4RUQ9vpL/KvgzpXh/bSf0B7DvEt+It2Qiws+RXH\nsc/cdxEDNRVwBOeueqSFZjw83fg5p3G5mMKUG0MLiNk0gw3S7JZKRndKiyKQZ7UCpfiRsk\nYJLZGGP8BeUGC5nYLicZAYJLddHyoq8jp8AtlGcKxgAQfWTjjlI2ppJPYpM2yncgnpv8Rk\nQiDH9onIjTxx715IDiLmsjesFi8weQrPXQ/OkBY0qud4A/piHiSg2/mcQ4bD+yNXP7ka7n\nhiGI/NPPSb/Q+a9SXa72VMI3z6s4/MKXL47t1NaNAAAFiMj8bezI/G3sAAAAB3NzaC1yc2\nEAAAGBAN4oF24KEwMnTzohG+FX0TzOHtuktemV4SBLfeb5GvMHXK7veyShZd8iuotEQC8p\nazAMo3qrl0zZ7WZtrp54DLmBNznhaGN3RNf8XFhFbvIBOgIhr++Es0LCrUL5apV4RfbNnF\ncnyS5reXL0D+s0cZsfCmmS+mzdzywzCLquC4rdwRJ/SjjfPfrlio16/Y1OvzJWkOI5/VWD\nHM4TA0KU8kzBYCAS7iWGuEVEPb6S/yr4M6V4f20n9Aew7xLfiLdkIsLPkVx7HP3HcRAzUV\ncATnrnqkhWY8PN34OadxuZjClBtDC4jZNIMN0uyWSkZ3SosikGe1AqX4kbJGCS2Rhj/AXl\nBguZ2C4nGQGCS3XR8qKvI6fALZRnCsYAEH1k445SNqaST2KTNsp3IJ6b/EZEIgx/aJyI08\nce9eSA4i5rI3rBYvMHkKz10PzpAWNKrneAP6Yh4koNv5nEOGw/sjVz+5Gu54YhiPzTz0m/\n0PmvUl2u9lTCN8+rOPzCly+O7dTWjQAAAAMBAAEAAAGBAKv82868C+YIG8UD9uKpKvrpFG\ni1BoR1HVn0N9+GAQAfNfjUvEAql4R9DXBeAVbBuRL05edFSpgbqzf+OA7FIAzJZajwwfEn\nV+vimtdXwcGng3I9BEjpMiLANoTANWzMNVYR7jRnP9ApMlf1bRGJg141VMlRGYEI46fzRp\nHHxnXWoe+hDiQjaIeCB5bqnbs1OL5O2FHb1S3LmJRNkduNFlyn5LRQE4CH4Mb3Qtn0UYnB\np7I2LGikYr9FkoDI/74CzZkL8OK01pfgSNbVrmJ3afFra7LrMYNUqseKsWIPvwCnHjr2hL\nLRxW6DU6CYZjy02ZBpkmISCBFSaLNbh0rH47B842lqrFPrEdGKBvlHJLHqzKDCBW+PSaHD\nK30kclgO1laxx0zdTUipYPPuJLL+2iHYWMYtKdDkpS96+BjoKKen0uZUhGamk2/rCbY1Gi\np/iWjNlDKExWjpnQd4KfyQvrds2KGj1+4loFLxT6akmi57aCj7rqKbiBfbaPuVUMp6HQAA\nAMEA8Jx79pkkCIhnA8DabHp3RBfRyaJvomka2O6XaiCdzs03U/9h8I6ROwJZ4bXXOar6n9\nKrPXC4gRIBuqxoFaBcTHNIwH5apzeptJrAXe4baKJIGjG9KWP5brbyGzOsqj0Lx1bFiiro\neDMOQCUFjpQ5DkwQTqpInzWLvt5bixMxQutqw4iirgB065j39UzgjPbFwI1+S/vTU2S68k\nqicpRmlz/hHjZX+wEPAwaUa9nBPPWv7tOqg0CYMFDwQbuT9WkuAAAAwQD28BUPrDCD4Sl+\nEI5kE6eyk9tdpTlMtgiLbH2WJO9yr4C/GVmQytujb6YXHAtZ3IXf8Wg6sXY8JH36dGTqZP\ngJaZfzhC4SEJvCUXWim/rdxj4CQMZW7o7guMME3w5hDHitj0vyGxatp30ltwUF6/gpSiEG\nSAI1lSNkZk3Ey0OMZv5Tp0Y5HJ3SBlnystYzzDvlDq4m+cNCmH0IytZV8Udwjkd7knLQce\ngvO+vKTWf6l8nb3BlBYBV72tyKGfd9pSMAAADBAOZPMjJZtQ5yzoklmyxqUiSZl/+aB41H\nIHU89ejt7cynuzIvOi3HWJB4201Z0yaS19xX48httEyxk0MTb5oK1H6yYKAx7m9DVMCq2e\nAJRM42Hh1Eer5bh/wUqdbqrV6NWkiXP7s440ml8tAsVULCKqQPRyPo1UBkayudMBx3Ke0W\n2sKWZDMT7OzC3lR4QdyC8keLzJhfudnP5ZWstOWgkTkPoZZ6EZZBz2gMVMEVczGcMYLIub\neulFT3H8VUH5mIjwAAAAtoaWRkZUByaWRlcgECAwQFBg==\n-----END OPENSSH PRIVATE KEY-----\n"
  },
  {
    "path": "pkg/manifestgen/sourcesecret/testdata/rsa.pub",
    "content": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDeKBduChMDJ086IRvhV9E8zh7bpLXpleEgS33m+RrzB1yu73skoWXfIrqLREAvKWswDKN6q5dM2e1mba6eeAy5gTc54Whjd0TX/FxYRW7yAToCIa/vhLNCwq1C+WqVeEX2zZxXJ8kua3ly9A/rNHGbHwppkvps3c8sMwi6rguK3cESf0o43z365YqNev2NTr8yVpDiOf1VgxzOEwNClPJMwWAgEu4lhrhFRD2+kv8q+DOleH9tJ/QHsO8S34i3ZCLCz5Fcexz9x3EQM1FXAE5656pIVmPDzd+DmncbmYwpQbQwuI2TSDDdLslkpGd0qLIpBntQKl+JGyRgktkYY/wF5QYLmdguJxkBgkt10fKiryOnwC2UZwrGABB9ZOOOUjamkk9ikzbKdyCem/xGRCIMf2iciNPHHvXkgOIuayN6wWLzB5Cs9dD86QFjSq53gD+mIeJKDb+ZxDhsP7I1c/uRrueGIYj8089Jv9D5r1JdrvZUwjfPqzj8wpcvju3U1o0=\n"
  },
  {
    "path": "pkg/manifestgen/sync/options.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sync\n\nimport (\n\t\"time\"\n)\n\ntype Options struct {\n\tInterval          time.Duration\n\tURL               string\n\tName              string\n\tNamespace         string\n\tBranch            string\n\tTag               string\n\tSemVer            string\n\tCommit            string\n\tSecret            string\n\tTargetPath        string\n\tManifestFile      string\n\tRecurseSubmodules bool\n}\n\nfunc MakeDefaultOptions() Options {\n\treturn Options{\n\t\tInterval:     1 * time.Minute,\n\t\tURL:          \"\",\n\t\tName:         \"flux-system\",\n\t\tNamespace:    \"flux-system\",\n\t\tBranch:       \"main\",\n\t\tSecret:       \"flux-system\",\n\t\tManifestFile: \"gotk-sync.yaml\",\n\t\tTargetPath:   \"\",\n\t}\n}\n"
  },
  {
    "path": "pkg/manifestgen/sync/sync.go",
    "content": "/*\nCopyright 2020 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sync\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/yaml\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n)\n\nfunc Generate(options Options) (*manifestgen.Manifest, error) {\n\tgvk := sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)\n\tgitRef := &sourcev1.GitRepositoryRef{}\n\tif options.Branch != \"\" {\n\t\tgitRef.Branch = options.Branch\n\t}\n\tif options.Tag != \"\" {\n\t\tgitRef.Tag = options.Tag\n\t}\n\tif options.SemVer != \"\" {\n\t\tgitRef.SemVer = options.SemVer\n\t}\n\tif options.Commit != \"\" {\n\t\tgitRef.Commit = options.Commit\n\t}\n\n\tgitRepository := sourcev1.GitRepository{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      options.Name,\n\t\t\tNamespace: options.Namespace,\n\t\t},\n\t\tSpec: sourcev1.GitRepositorySpec{\n\t\t\tURL: options.URL,\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: options.Interval,\n\t\t\t},\n\t\t\tReference: gitRef,\n\t\t\tSecretRef: &meta.LocalObjectReference{\n\t\t\t\tName: options.Secret,\n\t\t\t},\n\t\t\tRecurseSubmodules: options.RecurseSubmodules,\n\t\t},\n\t}\n\n\tgitData, err := yaml.Marshal(gitRepository)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgvk = kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)\n\tkustomization := kustomizev1.Kustomization{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       gvk.Kind,\n\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      options.Name,\n\t\t\tNamespace: options.Namespace,\n\t\t},\n\t\tSpec: kustomizev1.KustomizationSpec{\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: 10 * time.Minute,\n\t\t\t},\n\t\t\tPath:  fmt.Sprintf(\"./%s\", strings.TrimPrefix(options.TargetPath, \"./\")),\n\t\t\tPrune: true,\n\t\t\tSourceRef: kustomizev1.CrossNamespaceSourceReference{\n\t\t\t\tKind: sourcev1.GitRepositoryKind,\n\t\t\t\tName: options.Name,\n\t\t\t},\n\t\t},\n\t}\n\n\tksData, err := yaml.Marshal(kustomization)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &manifestgen.Manifest{\n\t\tPath:    path.Join(options.TargetPath, options.Namespace, options.ManifestFile),\n\t\tContent: fmt.Sprintf(\"%s\\n---\\n%s---\\n%s\", manifestgen.GenWarning, resourceToString(gitData), resourceToString(ksData)),\n\t}, nil\n}\n\nfunc resourceToString(data []byte) string {\n\tdata = bytes.Replace(data, []byte(\"  creationTimestamp: null\\n\"), []byte(\"\"), 1)\n\tdata = bytes.Replace(data, []byte(\"status: {}\\n\"), []byte(\"\"), 1)\n\treturn string(data)\n}\n"
  },
  {
    "path": "pkg/manifestgen/sync/sync_test.go",
    "content": "//go:build !e2e\n// +build !e2e\n\n/*\nCopyright 2020 The Flux CD contributors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sync\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nfunc TestGenerate(t *testing.T) {\n\topts := MakeDefaultOptions()\n\toutput, err := Generate(opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, apiVersion := range []string{sourcev1.GroupVersion.String(), kustomizev1.GroupVersion.String()} {\n\t\tif !strings.Contains(output.Content, apiVersion) {\n\t\t\tt.Errorf(\"apiVersion '%s' not found\", apiVersion)\n\t\t}\n\t}\n\n\tfmt.Println(output.Content)\n}\n"
  },
  {
    "path": "pkg/manifestgen/tmpdir.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage manifestgen\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\n// MkdirTempAbs creates a tmp dir and returns the absolute path to the dir.\n// This is required since certain OSes like MacOS create temporary files in\n// e.g. `/private/var`, to which `/var` is a symlink.\nfunc MkdirTempAbs(dir, pattern string) (string, error) {\n\ttmpDir, err := os.MkdirTemp(dir, pattern)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\ttmpDir, err = filepath.EvalSymlinks(tmpDir)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error evaluating symlink: %w\", err)\n\t}\n\treturn tmpDir, nil\n}\n"
  },
  {
    "path": "pkg/printers/dyff.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage printers\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/homeport/dyff/pkg/dyff\"\n)\n\n// DyffPrinter is a printer that prints dyff reports.\ntype DyffPrinter struct {\n\tOmitHeader bool\n}\n\n// NewDyffPrinter returns a new DyffPrinter.\nfunc NewDyffPrinter() *DyffPrinter {\n\treturn &DyffPrinter{\n\t\tOmitHeader: true,\n\t}\n}\n\n// Print prints the given args to the given writer.\nfunc (p *DyffPrinter) Print(w io.Writer, args ...interface{}) error {\n\tfor _, arg := range args {\n\t\tswitch arg := arg.(type) {\n\t\tcase dyff.Report:\n\t\t\treportWriter := &dyff.HumanReport{\n\t\t\t\tReport:     arg,\n\t\t\t\tOmitHeader: p.OmitHeader,\n\t\t\t}\n\n\t\t\tif err := reportWriter.WriteReport(w); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to print report: %w\", err)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported type %T\", arg)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/printers/interface.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage printers\n\nimport \"io\"\n\n// Printer is an interface for printing Flux cmd\toutputs.\ntype Printer interface {\n\t// Print prints the given args to the given writer.\n\tPrint(io.Writer, ...interface{}) error\n}\n\n// PrinterFunc is a function that can print args to a writer.\ntype PrinterFunc func(w io.Writer, args ...interface{}) error\n\n// Print implements Printer\nfunc (fn PrinterFunc) Print(w io.Writer, args ...interface{}) error {\n\treturn fn(w, args)\n}\n"
  },
  {
    "path": "pkg/printers/table_printer.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage printers\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/olekukonko/tablewriter\"\n)\n\n// TablePrinter is a printer that prints Flux cmd outputs.\nfunc TablePrinter(header []string) PrinterFunc {\n\treturn func(w io.Writer, args ...interface{}) error {\n\t\tvar rows [][]string\n\t\tfor _, arg := range args {\n\t\t\tswitch arg := arg.(type) {\n\t\t\tcase []interface{}:\n\t\t\t\tfor _, v := range arg {\n\t\t\t\t\ts, ok := v.([][]string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn fmt.Errorf(\"unsupported type %T\", v)\n\t\t\t\t\t}\n\t\t\t\t\trows = append(rows, s...)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn fmt.Errorf(\"unsupported type %T\", arg)\n\t\t\t}\n\t\t}\n\n\t\ttable := tablewriter.NewWriter(w)\n\t\ttable.SetHeader(header)\n\t\ttable.SetAutoWrapText(false)\n\t\ttable.SetAutoFormatHeaders(true)\n\t\ttable.SetHeaderAlignment(tablewriter.ALIGN_LEFT)\n\t\ttable.SetAlignment(tablewriter.ALIGN_LEFT)\n\t\ttable.SetCenterSeparator(\"\")\n\t\ttable.SetColumnSeparator(\"\")\n\t\ttable.SetRowSeparator(\"\")\n\t\ttable.SetHeaderLine(false)\n\t\ttable.SetBorder(false)\n\t\ttable.SetTablePadding(\"\\t\")\n\t\ttable.SetNoWhiteSpace(true)\n\t\ttable.AppendBulk(rows)\n\t\ttable.Render()\n\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/status/status.go",
    "content": "/*\nCopyright 2020, 2021 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage status\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/fluxcd/cli-utils/pkg/kstatus/polling\"\n\t\"github.com/fluxcd/cli-utils/pkg/kstatus/polling/aggregator\"\n\t\"github.com/fluxcd/cli-utils/pkg/kstatus/polling/collector\"\n\t\"github.com/fluxcd/cli-utils/pkg/kstatus/polling/event\"\n\t\"github.com/fluxcd/cli-utils/pkg/kstatus/status\"\n\t\"github.com/fluxcd/cli-utils/pkg/object\"\n\truntimeclient \"github.com/fluxcd/pkg/runtime/client\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/log\"\n)\n\ntype StatusChecker struct {\n\tpollInterval time.Duration\n\ttimeout      time.Duration\n\tclient       client.Client\n\tstatusPoller *polling.StatusPoller\n\tlogger       log.Logger\n}\n\nfunc NewStatusCheckerWithClient(c client.Client, pollInterval time.Duration, timeout time.Duration, log log.Logger) (*StatusChecker, error) {\n\treturn &StatusChecker{\n\t\tpollInterval: pollInterval,\n\t\ttimeout:      timeout,\n\t\tclient:       c,\n\t\tstatusPoller: polling.NewStatusPoller(c, c.RESTMapper(), polling.Options{}),\n\t\tlogger:       log,\n\t}, nil\n}\n\nfunc NewStatusChecker(kubeConfig *rest.Config, pollInterval time.Duration, timeout time.Duration, log log.Logger) (*StatusChecker, error) {\n\trestMapper, err := runtimeclient.NewDynamicRESTMapper(kubeConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc, err := client.New(kubeConfig, client.Options{Mapper: restMapper})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn NewStatusCheckerWithClient(c, pollInterval, timeout, log)\n}\n\nfunc (sc *StatusChecker) Assess(identifiers ...object.ObjMetadata) error {\n\tctx, cancel := context.WithTimeout(context.Background(), sc.timeout)\n\tdefer cancel()\n\n\topts := polling.PollOptions{PollInterval: sc.pollInterval}\n\teventsChan := sc.statusPoller.Poll(ctx, identifiers, opts)\n\n\tcoll := collector.NewResourceStatusCollector(identifiers)\n\tdone := coll.ListenWithObserver(eventsChan, desiredStatusNotifierFunc(cancel, status.CurrentStatus))\n\n\t<-done\n\n\t// we use sorted identifiers to loop over the resource statuses because a Go's map is unordered.\n\t// sorting identifiers by object's name makes sure that the logs look stable for every run\n\tsort.SliceStable(identifiers, func(i, j int) bool {\n\t\treturn strings.Compare(identifiers[i].Name, identifiers[j].Name) < 0\n\t})\n\tfor _, id := range identifiers {\n\t\trs := coll.ResourceStatuses[id]\n\t\tswitch rs.Status {\n\t\tcase status.CurrentStatus:\n\t\t\tsc.logger.Successf(\"%s: %s ready\", rs.Identifier.Name, strings.ToLower(rs.Identifier.GroupKind.Kind))\n\t\tcase status.NotFoundStatus:\n\t\t\tsc.logger.Failuref(\"%s: %s not found\", rs.Identifier.Name, strings.ToLower(rs.Identifier.GroupKind.Kind))\n\t\tdefault:\n\t\t\tsc.logger.Failuref(\"%s: %s not ready\", rs.Identifier.Name, strings.ToLower(rs.Identifier.GroupKind.Kind))\n\t\t}\n\t}\n\n\tif coll.Error != nil || ctx.Err() == context.DeadlineExceeded {\n\t\treturn fmt.Errorf(\"timed out waiting for all resources to be ready\")\n\t}\n\treturn nil\n}\n\n// desiredStatusNotifierFunc returns an Observer function for the\n// ResourceStatusCollector that will cancel the context (using the cancelFunc)\n// when all resources have reached the desired status.\nfunc desiredStatusNotifierFunc(cancelFunc context.CancelFunc,\n\tdesired status.Status) collector.ObserverFunc {\n\treturn func(rsc *collector.ResourceStatusCollector, _ event.Event) {\n\t\tvar rss []*event.ResourceStatus\n\t\tfor _, rs := range rsc.ResourceStatuses {\n\t\t\trss = append(rss, rs)\n\t\t}\n\t\taggStatus := aggregator.AggregateStatus(rss, desired)\n\t\tif aggStatus == desired {\n\t\t\tcancelFunc()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/uninstall/uninstall.go",
    "content": "/*\nCopyright 2022 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage uninstall\n\nimport (\n\t\"context\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tnetworkingv1 \"k8s.io/api/networking/v1\"\n\trbacv1 \"k8s.io/api/rbac/v1\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/errors\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\tautov1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\timagev1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tnotificationv1 \"github.com/fluxcd/notification-controller/api/v1\"\n\tnotificationv1b3 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\tswapi \"github.com/fluxcd/source-watcher/api/v2/v1beta1\"\n\n\t\"github.com/fluxcd/flux2/v2/pkg/log\"\n\t\"github.com/fluxcd/flux2/v2/pkg/manifestgen\"\n)\n\n// Components removes all Kubernetes components that are part of Flux excluding the CRDs and namespace.\nfunc Components(ctx context.Context, logger log.Logger, kubeClient client.Client, namespace string, dryRun bool) error {\n\tvar aggregateErr []error\n\topts, dryRunStr := getDeleteOptions(dryRun)\n\tselector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}\n\t{\n\t\tvar list appsv1.DeploymentList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tif err := kubeClient.Delete(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"Deployment/%s/%s deletion failed: %s\", r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"Deployment/%s/%s deleted %s\", r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list corev1.ServiceList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tif err := kubeClient.Delete(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"Service/%s/%s deletion failed: %s\", r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"Service/%s/%s deleted %s\", r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list networkingv1.NetworkPolicyList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tif err := kubeClient.Delete(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"NetworkPolicy/%s/%s deletion failed: %s\", r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"NetworkPolicy/%s/%s deleted %s\", r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list corev1.ServiceAccountList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tif err := kubeClient.Delete(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"ServiceAccount/%s/%s deletion failed: %s\", r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"ServiceAccount/%s/%s deleted %s\", r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list rbacv1.ClusterRoleList\n\t\tif err := kubeClient.List(ctx, &list, selector); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tif err := kubeClient.Delete(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"ClusterRole/%s deletion failed: %s\", r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"ClusterRole/%s deleted %s\", r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list rbacv1.ClusterRoleBindingList\n\t\tif err := kubeClient.List(ctx, &list, selector); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tif err := kubeClient.Delete(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"ClusterRoleBinding/%s deletion failed: %s\", r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"ClusterRoleBinding/%s deleted %s\", r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn errors.Reduce(errors.Flatten(errors.NewAggregate(aggregateErr)))\n}\n\n// Finalizers removes all finalizes on Kubernetes components that have been added by a Flux controller.\nfunc Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client, dryRun bool) error {\n\tvar aggregateErr []error\n\topts, dryRunStr := getUpdateOptions(dryRun)\n\t{\n\t\tvar list sourcev1.GitRepositoryList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list sourcev1.OCIRepositoryList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list sourcev1.HelmRepositoryList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list sourcev1.HelmChartList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list sourcev1.BucketList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list kustomizev1.KustomizationList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list helmv2.HelmReleaseList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list notificationv1b3.AlertList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list notificationv1b3.ProviderList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list notificationv1.ReceiverList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list imagev1.ImagePolicyList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list imagev1.ImageRepositoryList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list autov1.ImageUpdateAutomationList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tvar list swapi.ArtifactGeneratorList\n\t\tif err := kubeClient.List(ctx, &list, client.InNamespace(\"\")); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tr.Finalizers = []string{}\n\t\t\t\tif err := kubeClient.Update(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"%s/%s/%s removing finalizers failed: %s\", r.Kind, r.Namespace, r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"%s/%s/%s finalizers deleted %s\", r.Kind, r.Namespace, r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn errors.Reduce(errors.Flatten(errors.NewAggregate(aggregateErr)))\n}\n\n// CustomResourceDefinitions removes all Kubernetes CRDs that are a part of Flux.\nfunc CustomResourceDefinitions(ctx context.Context, logger log.Logger, kubeClient client.Client, dryRun bool) error {\n\tvar aggregateErr []error\n\topts, dryRunStr := getDeleteOptions(dryRun)\n\tselector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}\n\t{\n\t\tvar list apiextensionsv1.CustomResourceDefinitionList\n\t\tif err := kubeClient.List(ctx, &list, selector); err == nil {\n\t\t\tfor i := range list.Items {\n\t\t\t\tr := list.Items[i]\n\t\t\t\tif err := kubeClient.Delete(ctx, &r, opts); err != nil {\n\t\t\t\t\tlogger.Failuref(\"CustomResourceDefinition/%s deletion failed: %s\", r.Name, err.Error())\n\t\t\t\t\taggregateErr = append(aggregateErr, err)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.Successf(\"CustomResourceDefinition/%s deleted %s\", r.Name, dryRunStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn errors.Reduce(errors.Flatten(errors.NewAggregate(aggregateErr)))\n}\n\n// Namespace removes the namespace Flux is installed in.\nfunc Namespace(ctx context.Context, logger log.Logger, kubeClient client.Client, namespace string, dryRun bool) error {\n\tvar aggregateErr []error\n\topts, dryRunStr := getDeleteOptions(dryRun)\n\tns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}\n\tif err := kubeClient.Delete(ctx, &ns, opts); err != nil {\n\t\tlogger.Failuref(\"Namespace/%s deletion failed: %s\", namespace, err.Error())\n\t\taggregateErr = append(aggregateErr, err)\n\t} else {\n\t\tlogger.Successf(\"Namespace/%s deleted %s\", namespace, dryRunStr)\n\t}\n\treturn errors.Reduce(errors.Flatten(errors.NewAggregate(aggregateErr)))\n}\n\nfunc getDeleteOptions(dryRun bool) (*client.DeleteOptions, string) {\n\topts := &client.DeleteOptions{}\n\tvar dryRunStr string\n\tif dryRun {\n\t\tclient.DryRunAll.ApplyToDelete(opts)\n\t\tdryRunStr = \"(dry run)\"\n\t}\n\n\treturn opts, dryRunStr\n}\n\nfunc getUpdateOptions(dryRun bool) (*client.UpdateOptions, string) {\n\topts := &client.UpdateOptions{}\n\tvar dryRunStr string\n\tif dryRun {\n\t\tclient.DryRunAll.ApplyToUpdate(opts)\n\t\tdryRunStr = \"(dry run)\"\n\t}\n\n\treturn opts, dryRunStr\n}\n"
  },
  {
    "path": "rfcs/0001-authorization/README.md",
    "content": "# RFC-0001 Memorandum on Flux Authorization\n\n## Summary\n\nThis RFC describes in detail, for [Flux version 0.24][] (Nov 2021), how Flux determines which\noperations are allowed to proceed, and how this interacts with Kubernetes' access control.\n\n## Motivation\n\nTo this point, the Flux project has provided [examples of how to make a multi-tenant\nsystem](https://github.com/fluxcd/flux2-multi-tenancy/tree/v0.1.0), but not explained exactly how\nthey relate to Flux's authorization model; nor has the authorization model itself been\ndocumented. Further work on support for multi-tenancy, among other things, requires a full account\nof Flux's authorization model as a baseline.\n\n### Goals\n\n- Give a comprehensive account of Flux's authorization model\n\n### Non-Goals\n\n- Justify the model as it stands; this RFC simply records the state as at v0.24.\n\n## Flux's authorization model\n\nThe Flux controllers undertake operations as specified by custom resources of the kinds defined in\nthe [Flux API][]. Most of the operations are through the Kubernetes API. Authorization for\noperations on external systems is not accounted for here.\n\nFlux controllers defer to [Kubernetes' native RBAC][k8s-rbac] and [namespace isolation][k8s-ns] to\ndetermine which operations are authorized, when processing the custom resources in the Flux API.\n\nIn general, **Kubernetes API operations are constrained by the service account under which each\ncontroller pod runs**. In the [default deployment of Flux][flux-rbac] each controller has its own\nservice account; and, the service accounts for the Kustomize controller and Helm controller have the\n[`cluster-admin` cluster role][k8s-cluster-admin] bound to it.\n\nBoth the Kustomize controller and the Helm controller create, update and delete arbitrary sets of\nconfiguration that they take as user input. For example, a Kustomization object that references a\nGitRepository is processed by taking whatever is in the specified Git repository and applying it to\nthe cluster. This is informally called \"syncing\", and these user-supplied configurations will be\ncalled \"sync configurations\" in the following.\n\nThere are five types of access that have a distinct treatment with respect to RBAC and namespace\nisolation:\n\n - reading and writing the Flux API object to be processed\n - accessing dependencies of a Flux API object; for example, a secret that holds a decryption key\n - accessing Flux API objects related to the object being processed; for example, a GitRepository\n   referenced by a Kustomization\n - creating, updating and deleting Flux API objects as part of processing; for example, each\n   `HelmRelease` object contains a template for a Helm chart spec, which the Helm controller uses to\n   create a `HelmChart` object\n - creating, updating, deleting, and health-checking of arbitrary objects as specified by _sync\n   configurations_ (as mentioned above).\n\nThis table summarises how these operations are subject to RBAC and namespace isolation.\n\n| Type of operation                              | Accessed via               | Namespace isolation          |\n|------------------------------------------------|----------------------------|------------------------------|\n| Reading and writing the object to be processed | Controller service account | N/A                          |\n| Dependencies of object to be processed         | Controller service account | Same namespace only          |\n| Access to related Flux API objects             | Controller service account | Some cross-namespace refs[1] |\n| CRUD of Flux API objects                       | Controller service account | Created in same namespace    |\n| CRUD and healthcheck of sync configurations    | Impersonation[2]           | As directed by spec[2]       |\n\n[1] See \"Cross-namespace references\" below<br>\n[2] See \"Impersonation\" below\n\nThere are two related mechanisms that affect the service account used for the operations marked with\n\"Impersonation\" above: \"impersonation\" and \"remote apply\". These are explained in the following\nsections.\n\n### Impersonation\n\nThe Kustomize controller and Helm controller both apply arbitrary sets of Kubernetes configuration\n(\"_synced configuration_\" as above) to a cluster. These controllers use the service account named in\nthe field `.spec.serviceAccountName` in the `Kustomization` and `HelmRelease` objects respectively,\nwhile applying and health-checking the synced configuration. This mechanism is called\n\"impersonation\".\n\nThe `.spec.serviceAccountName` field is optional. If empty, the controller's service account is\nused.\n\n### Remote apply\n\nThe Kustomize controller and Helm controller are able to apply a set of configuration to a cluster\nother than the cluster in which they run. If the `Kustomization` or `HelmRelease` object [refers to\na secret containing a \"kubeconfig\" file][kubeconfig], the controller will construct a client using\nthat kubeconfig, and the client is used to apply the prepared set of configuration. The effect of\nthis is that the configuration will be applied as the user given in the kubeconfig; often this is a\nuser with the `cluster-admin` role bound to it, but not necessarily so.\n\nAll accesses that would use impersonation use the remote client instead.\n\n### Cross-namespace references\n\nSome Flux API kinds have fields which can refer to a Flux API object in another namespace. The Flux\ncontrollers do not respect namespace isolation when dereferencing these fields. The following are\nfields that are not restricted to the namespace of the containing object, listed by API kind.\n\n| API kind | field | explanation |\n|----------|-------|-------------|\n| **`kustomizations.kustomize.toolkit.fluxcd.io/v1beta2`** | `.spec.dependsOn` | Items are references that can include a namespace |\n|                                                          | `.spec.healthChecks` | Items are references that can include a namespace (note: these are accessed using impersonation) |\n|                                                          | `.spec.sourceRef` | This is a reference that can include a namespace |\n|                                                          | `.spec.targetNamespace` | This sets or overrides the namespace given in the top-most `kustomization.yaml` |\n| **`helmreleases.helm.toolkit.fluxcd/v2beta1`** | `.spec.dependsOn` | Items are references that can include a namespace |\n|                                                | `.spec.targetNamespace` | This gives the namespace into which a Helm chart is installed (note: using impersonation) |\n|                                                | `.spec.storageNamespace` | This gives the namespace in which the record of a Helm install is created (note: using impersonation) |\n|                                                | `.spec.chart.spec.sourceRef` | This is a reference (in the created `HelmChart` object) that can include a namespace |\n| **`alerts.notification.toolkit.fluxcd.io/v1beta2`** | `.spec.eventSources` | Items are references that can include a namespace |\n| **`receivers.notification.toolkit.fluxcd.io/v1beta2`** | `.spec.resources` | Items in this field are references that can include a namespace |\n| **`imagepolicies.image.toolkit.fluxcd.io/v1beta1`** | `.spec.imageRepositoryRef` | This reference can include a namespace[1] |\n\n[1] This particular cross-namespace reference is subject to additional access control; see \"Access\ncontrol for cross-namespace references\" below.\n\nNote that the field `.spec.sourceRef` of **`imageupdateautomation.image.toolkit.fluxcd.io`** does\n_not_ include a namespace.\n\n#### Access control for cross-namespace references\n\nIn v0.24, an `ImagePolicy` object can refer to a `ImageRepository` object in another\nnamespace. Unlike most cross-namespace references, the controller processing `ImagePolicy` objects\napplies additional access control, as given in the referenced `ImageRepository`: the field\n[`.spec.accessFrom`][access-from-ref] grants access to the namespaces selected therein. Access is\ndenied unless granted.\n\n## Security considerations\n\n### Impersonation is optional\n\nFlux does not insist on a service account to be supplied in `Kustomization` and `HelmRelease`\nspecifications, and the default is to use the controller's service account. That means a user with\nthe ability to create either of those objects can trivially arrange for a configuration to be\napplied with the controller service account, which in the default deployment of Flux will have\n`cluster-admin` bound to it. This represents a privilege escalation vulnerability in the default\ndeployment of Flux. To guard against it, an admission controller can be used to make the\n`.spec.serviceAccountName` field mandatory; an example which uses Kyverno is given in [the\nmulti-tenancy implementation][multi-tenancy-eg].\n\n### Cross-namespace references side-step namespace isolation\n\n`HelmRelease` and `Kustomization` objects can refer to `GitRepository`, `HelmRepository`, or\n`Bucket` (collectively \"sources\") in any other namespace. The referenced objects are accessed\nthrough the controller's service account, which by default has `cluster-admin` bound to it. This\nmeans all sources in a cluster are by default usable as a synced configuration, from any\nnamespace. To restrict access, an admission controller can be used to block cross-namespace\nreferences; the [example using Kyverno][multi-tenancy-eg] from above also does this.\n\n## References\n\n-  [CVE-2021-41254](https://github.com/fluxcd/kustomize-controller/security/advisories/GHSA-35rf-v2jv-gfg7)\n  \"Privilege escalation to cluster admin on multi-tenant environments\" was fixed in flux2 **v0.15.0**.\n\n[Flux version 0.24]: https://github.com/fluxcd/flux2/releases/tag/v0.24.0\n[serviceAccountName]: https://fluxcd.io/docs/components/kustomize/api/#kustomize.toolkit.fluxcd.io/v1beta2.KustomizationSpec\n[kubeconfig]: https://fluxcd.io/docs/components/kustomize/api/#kustomize.toolkit.fluxcd.io/v1beta2.KubeConfig\n[access-from-ref]: https://fluxcd.io/docs/components/image/imagerepositories/#allow-cross-namespace-references\n[Flux API]: https://fluxcd.io/docs/components/\n[flux-rbac]: https://github.com/fluxcd/flux2/tree/v0.24.0/manifests/rbac\n[k8s-ns]: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/\n[k8s-rbac]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/\n[k8s-cluster-admin]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles\n[multi-tenancy-eg]: https://github.com/fluxcd/flux2-multi-tenancy/blob/main/infrastructure/kyverno-policies/flux-multi-tenancy.yaml\n"
  },
  {
    "path": "rfcs/0002-helm-oci/README.md",
    "content": "# RFC-0002 Flux OCI support for Helm\n\n**Status:** implemented (partially)\n\n**Creation date:** 2022-03-30\n\n**Last update:** 2023-11-28\n\n## Summary\n\nGiven that Helm v3.8 supports [OCI](https://helm.sh/docs/topics/registries/) for package distribution,\nwe should extend the Flux Source API to allow fetching Helm charts from container registries.\n\n## Motivation\n\nHelm OCI support is one of the most requested feature in Flux\nas seen on this [issue](https://github.com/fluxcd/source-controller/issues/124).\n\nWith OCI support, Flux users can automate chart updates to Git in the same way\nthey do today for container images.\n\n### Goals\n\n- Add support for fetching Helm charts stored as OCI artifacts with minimal API changes to Flux.\n- Add support for verifying the authenticity of Helm OCI charts signed with Cosign.\n- Make it easy for users to switch from [HTTP/S Helm repositories](https://github.com/helm/helm-www/blob/416fabea6ffab8dc156b6a0c5eb5e8df5f5ef7dc/content/en/docs/topics/chart_repository.md)\n  to OCI repositories.\n\n### Non-Goals\n\n- Introduce a new API kind for referencing charts stored as OCI artifacts.\n\n## Proposal\n\nIntroduce an optional field called `type` to the `HelmRepository` spec.\nWhen not specified, the `spec.type` field defaults to `default` which preserve the current `HelmRepository` API behaviour.\nWhen the `spec.type` field is set to `oci`, the `spec.url` field must be prefixed with `oci://` (to follow the Helm conventions).\nFor `oci://` URLs, source-controller will use the Helm SDK and the `oras` library to connect to the OCI remote storage.\n\nIntroduce an optional field called `provider` for\n[context-based authorization](https://fluxcd.io/flux/security/contextual-authorization/)\nto AWS, Azure and Google Cloud. The `spec.provider` is ignored when `spec.type` is set to `default`.\n\n### Pull charts from private repositories\n\n#### Basic auth\n\nFor private repositories hosted on GitHub, Quay, self-hosted Docker Registry and others,\nthe credentials can be supplied with:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1beta2\nkind: HelmRepository\nmetadata:\n  name: <repo-name>\nspec:\n  type: oci\n  secretRef:\n    name: regcred\n```\n\nThe `secretRef` points to a Kubernetes secret in the same namespace as the `HelmRepository`.\nThe [secret type](https://kubernetes.io/docs/concepts/configuration/secret/#secret-types)\nmust be `kubernetes.io/dockerconfigjson`:\n\n```shell\nkubectl create secret docker-registry regcred \\\n  --docker-server=<your-registry-server> \\\n  --docker-username=<your-name> \\\n  --docker-password=<your-pword>\n```\n\n#### OIDC auth\n\nWhen Flux runs on AKS, EKS or GKE, an IAM role (that grants read-only access to ACR, ECR or GCR)\ncan be used to bind the `source-controller` to the IAM role.\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1beta2\nkind: HelmRepository\nmetadata:\n  name: <repo-name>\nspec:\n  type: oci\n  provider: azure\n```\n\nThe provider accepts the following values: `generic`, `aws`, `azure` and `gcp`. When the provider is\nnot specified, it defaults to `generic`. When the provider is set to `aws`, `azure` or `gcp`, the\ncontroller will use a specific cloud SDK for authentication purposes.\n\nIf both `spec.secretRef` and a non-generic provider are present in the definition,\nthe controller will use the static credentials from the referenced secret.\n\n### Verify Helm charts\n\nTo verify the authenticity of the Helm OCI charts, Flux will use the Sigstore Go SDK and implement verification\nfor artifacts which were either signed with keys generated by Cosign or signed using the Cosign\n[keyless method](https://github.com/sigstore/cosign/blob/main/KEYLESS.md).\n\nTo enable signature verification, the Cosign public keys can be supplied with:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1beta2\nkind: HelmChart\nmetadata:\n  name: <chart-name>\nspec:\n  verify:\n    provider: cosign\n    secretRef:\n      name: cosign-public-keys\n```\n\nNote that the Kubernetes secret containing the Cosign public keys, must use `.pub` extension:\n\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: cosign-public-keys\ntype: Opaque\nstringData:\n  key1.pub: <pub-key-1>\n  key2.pub: <pub-key-2>\n```\n\nFor verifying public Helm charts which are signed using the keyless method,\nthe `spec.verify.secretRef` field must be omitted:\n\n```yaml\nspec:\n  verify:\n    provider: cosign\n```\n\nWhen using the keyless method, Flux will verify the signatures in the Rekor\ntransparency log instance hosted at [rekor.sigstore.dev](https://rekor.sigstore.dev/).\n\n### User Stories\n\n#### Story 1\n\n> As a developer I want to use Flux `HelmReleases` that refer to Helm charts stored\n> as OCI artifacts in GitHub Container Registry.\n\nFirst create a secret using a GitHub token that allows access to GHCR:\n\n```sh\nkubectl create secret docker-registry ghcr-charts \\\n    --docker-server=ghcr.io \\\n    --docker-username=$GITHUB_USER \\\n    --docker-password=$GITHUB_TOKEN\n```\n\nThen define a `HelmRepository` of type `oci` and reference the `dockerconfig` secret:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1beta2\nkind: HelmRepository\nmetadata:\n  name: ghcr-charts\n  namespace: default\nspec:\n  type: oci\n  url: oci://ghcr.io/my-org/charts/\n  secretRef:\n    name: ghcr-charts\n```\n\nAnd finally in Flux `HelmReleases`, refer to the ghcr-charts `HelmRepository`:\n\n```yaml\napiVersion: helm.toolkit.fluxcd.io/v2beta1\nkind: HelmRelease\nmetadata:\n  name: my-app\n  namespace: default\nspec:\n  interval: 60m\n  chart:\n    spec:\n      chart: my-app\n      version: '1.0.x'\n      sourceRef:\n        kind: HelmRepository\n        name: ghcr-charts\n      interval: 1m # check for new OCI artifacts every minute\n```\n\n#### Story 2\n\n> As a platform admin I want to automate Helm chart updates based on a semver ranges.\n> When a new patch version is available in the container registry, I want Flux to open a PR\n> with the version set in the `HelmRelease` manifests.\n\nGiven that charts are stored in container registries, you can use Flux image automation\nand patch the chart version in Git, in the same way Flux works for updating container image tags.\n\nDefine an image registry and a policy for the chart artifact:\n\n```yaml\napiVersion: image.toolkit.fluxcd.io/v1beta1\nkind: ImageRepository\nmetadata:\n  name: my-app\n  namespace: default\nspec:\n  image: ghcr.io/my-org/charts/my-app\n  interval: 1m0s\n---\napiVersion: image.toolkit.fluxcd.io/v1beta1\nkind: ImagePolicy\nmetadata:\n  name: my-app\n  namespace: default\nspec:\n  imageRepositoryRef:\n    name: my-app\n  policy:\n    semver:\n      range: 1.0.x\n```\n\nThen add the policy marker to the `HelmRelease` manifests in Git:\n\n```yaml\napiVersion: helm.toolkit.fluxcd.io/v2beta1\nkind: HelmRelease\nmetadata:\n  name: my-app\n  namespace: default\nspec:\n  interval: 60m\n  chart:\n    spec:\n      chart: my-app\n      version: 1.0.0 # {\"$imagepolicy\": \"default:my-app:tag\"}\n      sourceRef:\n        kind: HelmRepository\n        name: ghcr-charts\n      interval: 1m\n```\n\n### Alternatives\n\nWe could introduce a new API type e.g. `HelmRegistry` to hold the reference to auth secret,\nas proposed in [#2573](https://github.com/fluxcd/flux2/pull/2573).\nThat is considered unpractical, as there is no benefit for users in having a dedicated kind instead of\na `type` field in the current `HelmRepository` API. Adding a `type` field to the spec follows the Flux\nBucket API design, where the same Kind servers different implementations: AWS S3 vs Azure Blob vs Google Storage.\n\n## Design Details\n\nUnlike the default `HelmRepository`, the OCI `HelmRepository` does not need to\ndownload any repository index file. The associated HelmChart can pull the chart\ndirectly from the OCI registry based on the registry information in the\n`HelmRepository` object. This makes the `HelmRepository` of type `oci` static,\nnot backed by a reconciler to move to a desired state. It becomes a data\ncontainer with information about the OCI registry.\n\nIn source-controller, the `HelmRepositoryReconciler` will be updated to check\nthe `.spec.type` field of `HelmRepository` and do nothing if it is `oci`.\n\nThe current `HelmChartReconciler` will be adapted to handle both types.\n\n### Enabling the feature\n\nThe feature is enabled by default.\n\n## Implementation History\n\n* **2022-05-19** Partially implemented by [source-controller#690](https://github.com/fluxcd/source-controller/pull/690)\n* **2022-06-06** First implementation released with [flux2 v0.31.0](https://github.com/fluxcd/flux2/releases/tag/v0.31.0)\n* **2022-08-11** Resolve chart dependencies from OCI released with [flux2 v0.32.0](https://github.com/fluxcd/flux2/releases/tag/v0.32.0)\n* **2022-08-29** Contextual login for AWS, Azure and GCP released with [flux2 v0.33.0](https://github.com/fluxcd/flux2/releases/tag/v0.33.0)\n* **2022-10-21** Verifying Helm charts with Cosign released with [flux2 v0.36.0](https://github.com/fluxcd/flux2/releases/tag/v0.36.0)\n* **2023-11-28** Update the design of HelmRepository of type OCI to be static object [flux2 v2.2.0](https://github.com/fluxcd/flux2/releases/tag/v2.2.0)\n\n### TODOs\n\n* [Add support for container registries with self-signed TLS certs](https://github.com/fluxcd/source-controller/issues/723)\n"
  },
  {
    "path": "rfcs/0003-kubernetes-oci/README.md",
    "content": "# RFC-0003 Flux OCI support for Kubernetes manifests\n\n**Status:** implemented\n\n**Creation date:** 2022-03-31\n\n**Last update:** 2023-11-07\n\n## Summary\n\nFlux should be able to distribute and reconcile Kubernetes configuration packaged as OCI artifacts.\n\nOn the client-side, the Flux CLI should offer a command for packaging Kubernetes configs into\nan OCI artifact and pushing the artifact to a container registry using the Docker config file\nand the Docker credential helpers for authentication.\n\nOn the server-side, the Flux source-controller should offer a dedicated API Kind for defining\nhow OCI artifacts are pulled from container registries and how the artifact's authenticity can be verified.\nFlux should be able to work with any type of artifact even if it's not created with the Flux CLI.\n\n## Motivation\n\nGiven that OCI registries are evolving into a generic artifact storage solution,\nwe should extend Flux to allow fetching Kubernetes manifests and related configs\nfrom container registries similar to how Flux works with Git and Bucket storage.\n\nWith OCI support, Flux users can automate artifact updates to Git in the same way\nthey do today for container images.\n\n### Goals\n\n- Add support to the Flux CLI for packaging Kubernetes manifests and related configs into OCI artifacts.\n- Add support to Flux source-controller for fetching configs stored as OCI artifacts.\n- Make it easy for users to switch from Git repositories and Buckets to OCI repositories.\n\n### Non-Goals\n\n- Enforce a specific OCI media type for artifacts containing Kubernetes manifests or any other configs.\n\n## Proposal\n\n### Push artifacts\n\nFlux users should be able to package a local directory containing Kubernetes configs into a tarball\nand push the archive to a container registry as an OCI artifact.\n\n```sh\nflux push artifact oci://docker.io/org/app-config:v1.0.0 \\\n  --source=\"$(git config --get remote.origin.url)\" \\\n  --revision=\"sha1:$(git rev-parse HEAD)\" \\\n  --path=\"./deploy\"\n```\n\nThe Flux CLI will produce OCI artifacts by setting the config layer\nmedia type to `application/vnd.cncf.flux.config.v1+json`.\n\nThe directory pointed to by `--path` is archived and compressed in the `tar+gzip` format\nand the layer media type is set to `application/vnd.cncf.flux.content.v1.tar+gzip`.\n\nThe source and revision are added to the OCI artifact as Open Containers standard annotations:\n\n```json\n{\n  \"schemaVersion\": 2,\n  \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n  \"annotations\": {\n    \"org.opencontainers.image.created\": \"2023-02-10T09:06:09Z\",\n    \"org.opencontainers.image.revision\": \"sha1:6ea3e5b4da159fcb4a1288f072d34c3315644bcc\",\n    \"org.opencontainers.image.source\": \"https://github.com/fluxcd/flux2\"\n  }\n}\n```\n\nTo ease the promotion workflow of a specific version from one environment to another, the CLI\nshould offer a tagging command.\n\n```sh\nflux tag artifact oci://docker.io/org/app-config:v1.0.0 --tag=latest --tag=production\n```\n\nTo view all the available artifacts in a repository and their metadata, the CLI should\noffer a list command.\n\n```sh\nflux list artifacts oci://docker.io/org/app-config\n```\n\nTo help inspect artifacts, the Flux CLI will offer a `build` and a `pull` command for generating\ntarballs locally and for downloading the tarballs from remote container registries.\n\n```sh\nflux build artifact --path ./deploy --output tmp/artifact.tgz\nflux pull artifact oci://docker.io/org/app-config:v1.0.0 --output ./manifests\n```\n\n### Pull artifacts\n\nFlux users should be able to define a source for pulling manifests inside the cluster from an OCI repository.\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1beta2\nkind: OCIRepository\nmetadata:\n  name: app-config\n  namespace: flux-system\nspec:\n  interval: 10m\n  url: oci://docker.io/org/app-config\n  ref:\n    tag: v1.0.0\n```\n\nThe `spec.url` field points to the container image repository in the format `oci://<host>:<port>/<org-name>/<repo-name>`. \nNote that specifying a tag or digest is not in accepted for this field. The `spec.url` value is used by the controller\nto fetch the list of tags from the remote OCI repository.\n\nAn `OCIRepository` can refer to an artifact by tag, digest or semver range:\n\n```yaml\nspec:\n  ref:\n    # one of\n    tag: \"latest\"\n    digest: \"sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2\"\n    semver: \"6.0.x\"\n```\n\n### Layer selection\n\nBy default, Flux assumes that the first layer of the OCI artifact contains the Kubernetes configuration.\nFor multi-layer artifacts created by other tools than Flux CLI\n(e.g. [oras](https://github.com/oras-project/oras),\n[crane](https://github.com/google/go-containerregistry/tree/main/cmd/crane)),\nusers can specify the [media type](https://github.com/opencontainers/image-spec/blob/v1.0.2/media-types.md) of the layer\nwhich contains the tarball with Kubernetes manifests.\n\n```yaml\nspec:\n  layerSelector:\n    mediaType: \"application/vnd.cncf.flux.content.v1.tar+gzip\"\n```\n\nIf the layer selector matches more than one layer,\nthe first layer matching the specified media type will be used.\nNote that Flux requires that the OCI layer is\n[compressed](https://github.com/opencontainers/image-spec/blob/v1.0.2/layer.md#gzip-media-types)\nin the `tar+gzip` format.\n\n### Pull artifacts from private repositories\n\nFor authentication purposes, Flux users can choose between supplying static credentials with Kubernetes secrets\nand cloud-based OIDC using an IAM role binding to the source-controller Kubernetes service account.\n\n#### Basic auth\n\nFor private repositories hosted on DockerHub, GitHub, Quay, self-hosted Docker Registry and others,\nthe credentials can be supplied with:\n\n```yaml\nspec:\n  secretRef:\n    name: regcred\n```\n\nThe `secretRef` points to a Kubernetes secret in the same namespace as the `OCIRepository`,\nthe secret type must be `kubernetes.io/dockerconfigjson`:\n\n```shell\nkubectl create secret docker-registry regcred \\\n  --docker-server=<your-registry-server> \\\n  --docker-username=<your-name> \\\n  --docker-password=<your-pword>\n```\n\nFor image pull secrets attached to a service account, the account name can be specified with:\n\n```yaml\nspec:\n  serviceAccountName: regsa\n```\n\n#### Client cert auth\n\nFor private repositories which require a certificate to authenticate,\nthe client certificate, private key and the CA certificate (if self-signed), can be provided with:\n\n```yaml\nspec:\n  certSecretRef:\n    name: regcert\n```\n\nThe `certSecretRef` points to a Kubernetes secret in the same namespace as the `OCIRepository`:\n\n```shell\nkubectl create secret generic regcert \\\n  --from-file=certFile=client.crt \\\n  --from-file=keyFile=client.key \\\n  --from-file=caFile=ca.crt\n```\n\n#### OIDC auth\n\nWhen Flux runs on AKS, EKS or GKE, an IAM role (that grants read-only access to ACR, ECR or GCR)\ncan be used to bind the `source-controller` to the IAM role.\n\n```yaml\nspec:\n  provider: aws\n```\n\nThe provider accepts the following values: `generic`, `aws`, `azure` and `gcp`. When the provider is\nnot specified, it defaults to `generic`. When the provider is set to `aws`, `azure` or `gcp`, the \ncontroller will use a specific cloud SDK for authentication purposes. If both `spec.secretRef` and\na non-generic provider are present in the definition, the controller will use the static credentials\nfrom the referenced secret.\n\n### Verify artifacts\n\nTo verify the authenticity of the OCI artifacts, Flux will use the Sigstore Go SDK and implement verification\nfor artifacts which were either signed with keys generated by Cosign or signed using the Cosign\n[keyless method](https://github.com/sigstore/cosign/blob/main/KEYLESS.md).\n\nTo enable signature verification, the Cosign public key can be supplied with:\n\n```yaml\nspec:\n  verify:\n    provider: cosign\n    secretRef:\n      name: cosign-key\n```\n\nFor verifying public artifacts which are signed using the keyless method,\nthe `.spec.verify.matchOIDCIdentity` field must be used instead of\n `spec.verify.secretRef`.\n\n```yaml\nspec:\n  verify:\n    provider: cosign\n    matchOIDCIdentity:\n      - issuer: \"^https://token.actions.githubusercontent.com$\"\n        subject: \"^https://github.com/org/app-repository.*$\"\n```\n\nThe `matchOIDCIdentity` entries must contain the following fields:\n\n- `.issuer`, regexp that matches against the OIDC issuer.\n- `.subject`, regexp that matches against the subject identity in the certificate.\n\nThe entries are evaluated in an OR fashion, i.e. the identity is deemed to be\nverified if any one entry successfully matches against the identity.\n\nWhen using the keyless method, Flux will verify the signatures in the Rekor\ntransparency log instance hosted at [rekor.sigstore.dev](https://rekor.sigstore.dev/).\n\n### Reconcile artifacts\n\nThe `OCIRepository` can be used as a drop-in replacement for `GitRepository` and `Bucket` sources.\nFor example, a Flux Kustomization can refer to an `OCIRepository` and reconcile the manifests found in the OCI artifact:\n\n```yaml\napiVersion: kustomize.toolkit.fluxcd.io/v1beta2\nkind: Kustomization\nmetadata:\n  name: app\n  namespace: flux-system\nspec:\n  interval: 10m\n  sourceRef:\n    kind: OCIRepository\n    name: app-config\n  path: ./\n```\n\n### User Stories\n\n#### Story 1\n\n> As a developer I want to publish my app Kubernetes manifests to the same GHCR registry\n> where I publish my app containers.\n\nFirst login to GHCR with Docker:\n\n```sh\ndocker login ghcr.io -u ${GITHUB_USER} -p ${GITHUB_TOKEN}\n```\n\nBuild your app container image and push it to GHCR:\n\n```sh\ndocker build -t ghcr.io/org/my-app:v1.0.0 .\ndocker push ghcr.io/org/my-app:v1.0.0\n```\n\nEdit the app deployment manifest and set the new image tag.\nThen push the Kubernetes manifests to GHCR:\n\n```sh\nflux push artifact oci://ghcr.io/org/my-app-config:v1.0.0 \\\n\t--source=\"$(git config --get remote.origin.url)\" \\\n\t--revision=\"sha1:$(git rev-parse HEAD)\"\\\n\t--path=\"./deploy\"\n```\n\nSign the config image with cosign:\n\n```sh\ncosign sign --key cosign.key ghcr.io/org/my-app-config:v1.0.0\n```\n\nMark `v1.0.0` as latest:\n\n```sh\nflux tag artifact oci://ghcr.io/org/my-app-config:v1.0.0 --tag latest\n```\n\nList the artifacts and their metadata with:\n\n```console\n$ flux list artifacts oci://ghcr.io/org/my-app-config\nARTIFACT                                DIGEST                                                                 \tSOURCE                                          REVISION                                      \nghcr.io/org/my-app-config:latest   \tsha256:45b95019d30af335137977a369ad56e9ea9e9c75bb01afb081a629ba789b890c\thttps://github.com/org/my-app-config.git   \tsha1:20b3a674391df53f05e59a33554973d1cbd4d549\t\nghcr.io/org/my-app-config:v1.0.0\tsha256:45b95019d30af335137977a369ad56e9ea9e9c75bb01afb081a629ba789b890c\thttps://github.com/org/my-app-config.git\tsha1:3f45e72f0d3457e91e3c530c346d86969f9f4034\t\n```\n\n#### Story 2\n\n> As a developer I want to deploy my app using Kubernetes manifests published as OCI artifacts to GHCR.\n\nFirst create a secret using a GitHub token that allows access to GHCR:\n\n```sh\nkubectl create secret docker-registry my-app-regcred \\\n    --docker-server=ghcr.io \\\n    --docker-username=$GITHUB_USER \\\n    --docker-password=$GITHUB_TOKEN\n```\n\nThen create a secret with your cosgin public key:\n\n```sh\nkubectl create secret generic my-app-cosgin-key \\\n    --from-file=cosign.pub=cosign/my-key.pub\n```\n\nThen define an `OCIRepository` to fetch and verify the latest app config version:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1beta2\nkind: OCIRepository\nmetadata:\n  name: app-config\n  namespace: default\nspec:\n  interval: 10m\n  url: oci://ghcr.io/org/my-app-config\n  ref:\n    semver: \"1.x\"\n  secretRef:\n    name: my-app-regcred\n  verify:\n    provider: cosign\n    secretRef:\n      name: my-app-cosgin-key\n```\n\nAnd finally, create a Flux Kustomization to reconcile the app on the cluster:\n\n```yaml\napiVersion: kustomize.toolkit.fluxcd.io/v1beta2\nkind: Kustomization\nmetadata:\n  name: app\n  namespace: default\nspec:\n  interval: 10m\n  sourceRef:\n    kind: OCIRepository\n    name: app-config\n  path: ./deploy\n  prune: true\n  wait: true\n  timeout: 2m\n```\n\n## Design Details\n\nThe Flux controllers and CLI will use the [fluxcd/pkg/oci](https://github.com/fluxcd/pkg/tree/main/oci)\nlibrary for OCI operations such as push, pull, tag, list tags, etc.\n\nFor authentication purposes, the `flux <verb> artifact` commands will use the `~/.docker/config.json`\nconfig file and the Docker credential helpers. On Cloud VMs without Docker installed, the CLI will\nuse context-based authorization for AWS, Azure and GCP.\n\nThe Flux CLI will produce OCI artifacts with the following format:\n\n```json\n{\n  \"schemaVersion\": 2,\n  \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n  \"config\": {\n    \"mediaType\": \"application/vnd.cncf.flux.config.v1+json\",\n    \"size\": 233,\n    \"digest\": \"sha256:1b80ecb1c04d4a9718a6094a00ed17b76ea8ff2bb846695fa38e7492d34f505c\"\n  },\n  \"layers\": [\n    {\n      \"mediaType\": \"application/vnd.cncf.flux.content.v1.tar+gzip\",\n      \"size\": 19081,\n      \"digest\": \"sha256:46c2b334705cd08db1a6fa46f860cd3364fc1a3636eea37a9b35537549086a1c\"\n    }\n  ],\n  \"annotations\": {\n    \"org.opencontainers.image.created\": \"2023-02-10T09:06:09Z\",\n    \"org.opencontainers.image.revision\": \"sha1:6ea3e5b4da159fcb4a1288f072d34c3315644bcc\",\n    \"org.opencontainers.image.source\": \"https://github.com/fluxcd/flux2\"\n  }\n}\n```\n\nThe source-controller will extract the first layer from the OCI artifact, and will repackage it\nas an internal `sourcev1.Artifact`. The internal artifact revision will be set to the OCI SHA256 digest\nand the OpenContainers annotation will be copied to the internal artifact metadata:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1beta2\nkind: OCIRepository\nmetadata:\n  creationTimestamp: \"2022-06-22T09:14:19Z\"\n  finalizers:\n  - finalizers.fluxcd.io\n  generation: 1\n  name: podinfo\n  namespace: oci\n  resourceVersion: \"6603\"\n  uid: 42e0b9f0-021c-476d-86c7-2cd20747bfff\nspec:\n  interval: 10m\n  ref:\n    tag: 6.1.6\n  timeout: 60s\n  url: oci://ghcr.io/stefanprodan/manifests/podinfo\nstatus:\n  artifact:\n    checksum: d7e924b4882e55b97627355c7b3d2e711e9b54303afa2f50c25377f4df66a83b\n    lastUpdateTime: \"2022-06-22T09:14:21Z\"\n    metadata:\n      org.opencontainers.image.created: \"2023-02-10T09:06:09Z\"\n      org.opencontainers.image.revision: sha1:b3b00fe35424a45d373bf4c7214178bc36fd7872\n      org.opencontainers.image.source: https://github.com/stefanprodan/podinfo.git\n    path: ocirepository/oci/podinfo/3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de.tar.gz\n    revision: sha256:3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de\n    size: 1105\n    url: http://source-controller.flux-system.svc.cluster.local./ocirepository/oci/podinfo/3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de.tar.gz\n  conditions:\n  - lastTransitionTime: \"2022-06-22T09:14:21Z\"\n    message: stored artifact for revision 'sha256:3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de'\n    observedGeneration: 1\n    reason: Succeeded\n    status: \"True\"\n    type: Ready\n  - lastTransitionTime: \"2022-06-22T09:14:21Z\"\n    message: stored artifact for revision 'sha256:3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de'\n    observedGeneration: 1\n    reason: Succeeded\n    status: \"True\"\n    type: ArtifactInStorage\n  observedGeneration: 1\n  url: http://source-controller.flux-system.svc.cluster.local./ocirepository/oci/podinfo/latest.tar.gz\n```\n\n### Enabling the feature\n\nThe feature is enabled by default.\n\n## Implementation History\n\n* **2022-08-08** Partially implemented by [source-controller#788](https://github.com/fluxcd/source-controller/pull/788)\n* **2022-08-11** First implementation released with [flux2 v0.32.0](https://github.com/fluxcd/flux2/releases/tag/v0.32.0)\n* **2022-08-29** Select layer by OCI media type released with [flux2 v0.33.0](https://github.com/fluxcd/flux2/releases/tag/v0.33.0)\n* **2022-09-29** Verifying OCI artifacts with Cosign released with [flux2 v0.35.0](https://github.com/fluxcd/flux2/releases/tag/v0.35.0)\n* **2023-02-20** Custom OCI media types released with [flux2 v0.40.0](https://github.com/fluxcd/flux2/releases/tag/v0.40.0)\n* **2023-10-31** OIDC identity verification implemented in \n  [source-controller#1250](https://github.com/fluxcd/source-controller/pull/1250)\n"
  },
  {
    "path": "rfcs/0004-insecure-http/README.md",
    "content": "# RFC-0004 Block insecure HTTP connections across Flux\n\n**Status:** implementable\n\n**Creation Date:** 2022-09-08\n\n**Last update:** 2023-07-26\n\n## Summary\n\nFlux should have a consistent way of disabling insecure HTTP connections.\n\nAt the controller level, a flag should be present which would disable all outgoing HTTP connections.\nAt the object level, a field should be provided which would enable the use of non-TLS endpoints.\n\nIf the use of a non-TLS endpoint is not supported, reconciliation will fail and the object will be marked\nas stalled, signalling that human intervention is required.\n\n## Motivation\n\nToday the use of non-TLS based connections is inconsistent across Flux controllers.\n\nControllers that deal only with `http` and `https` schemes have no way to block use of the `http` scheme at controller-level.\nSome Flux objects provide a `.spec.insecure` field to enable the use of non-TLS based endpoints, but they don't clearly notify\nusers when the option is not supported (e.g. Azure/GCP Buckets).\n\n### Goals\n\n* Provide a flag across relevant Flux controllers which disables all outgoing HTTP connections.\n* Add a field which enables the use of non-TLS endpoints to appropriate Flux objects.\n* Provide a way for users to be made aware that their use of non-TLS endpoints is not supported if that is the case.\n\n### Non-Goals\n\n* Break Flux's current behavior of allowing HTTP connections.\n* Change in behavior of communication between Flux components.\n\n## Proposal\n\n### Controllers\n\nFlux users should be able to enforce that controllers are using HTTPS connections only.\nThis shall be enabled by adding a new boolean flag `--insecure-allow-http` to the following controllers:\n* source-controller\n* notification-controller\n* image-automation-controller\n* image-reflector-controller\n\nThe default value of this flag shall be `true`. This would ensure that there is no breaking change with controllers\nstill being able to access non-TLS endpoints. To disable this behavior and enforce the use of HTTPS connections, users would\nhave to explicitly pass the flag to the controller:\n\n```yaml\nspec:\n  template:\n    spec:\n      containers:\n      - name: manager\n        image: fluxcd/source-controller\n        args:\n          - --watch-all-namespaces\n          - --log-level=info\n          - --log-encoding=json\n          - --enable-leader-election\n          - --storage-path=/data\n          - --storage-adv-addr=source-controller.$(RUNTIME_NAMESPACE).svc.cluster.local.\n          - --insecure-allow-http=false\n```\n\n**Note:** The flag shall not be added to the following controllers:\n* kustomize-controller: This flag is excluded from this controller, as the upstream `kubenetes-sigs/kustomize` project\ndoes not support disabling HTTP connections while fetching resources from remote bases. We can revisit this if the\nupstream project adds support for this at a later point in time.\n* helm-controller: This flag does not serve a purpose in this controller, as the controller does not make any HTTP calls.\nFurthermore although both controllers can also do remote applies, serving `kube-apiserver` over plain\nHTTP is disabled by default. While technically this can be enabled, the option for this configuration was also disabled\nquite a while back (ref: https://github.com/kubernetes/kubernetes/pull/65830/).\n\n### Objects\n\nSome Flux objects, like `GitRepository`, provide a field for specifying a URL, and the URL would contain the scheme.\nIn such cases, the scheme can be used for inferring the transport type of the connection and consequently,\nwhether to use HTTP or HTTPS connections for that object.\nBut there are a few objects that don't allow such behavior, for example:\n\n* `ImageRepository`: It provides a field, `.spec.image`, which is used for specifying the address of the image present on\na container registry. But any address containing a scheme is considered invalid and HTTPS is the default transport used.\nThis prevents users from using images present on insecure registries.\n* OCI `HelmRepository`: When using an OCI registry as a Helm repository, the `.spec.url` is expected to begin with `oci://`.\nSince the scheme part of the URL is used to specify the type of `HelmRepository`, there is no way for users to specify\nthat the registry is hosted at a non-TLS endpoint.\n\nFor such objects, we shall introduce a new boolean field `.spec.insecure`, which shall be `false` by default. Users that\nneed their object to point to an HTTP endpoint, can set this to `true`.\n\n### CLI\n\nThe Flux CLI offers several commands for creating Flux specific resources. Some of these commands may involve specifying\nan endpoint such as creating an `OCIRepository`:\n\n```sh\n flux create source oci podinfo \\\n    --url=oci://ghcr.io/stefanprodan/manifests/podinfo \\\n    --tag=6.1.6 \\\n    --interval=10m\n```\n\nSince these commands essentially create object definitions, the CLI should offer a boolean flag `--insecure`\nfor the required commands, which will be used for specifying the value of `.spec.insecure` of such objects.\n\n> Note: This flag should not be confused with `--insecure-skip-tls-verify` which is meant to skip TLS verification\n> when using an HTTPS connection.\n\n### Proxy\n\nThe flag shall also apply to all possible proxy configurations. If the flag `--insecure-allow-http` is set to\n`false`, then specifying the `HTTP_PROXY` environment variable to the controller will lead to the controller\nexiting with a failure on startup. This also applies for when the `HTTPS_PROXY` enviornment variable's value is\na URL that has `http` as its scheme.\n\nSimilarly, if a proxy is specified using the object's API, such as through `.spec.secretRef` in `Provider` in the\n`notification.toolkit.fluxcd.io` API group and the proxy URL has `http` as its scheme, the reconciler will fail and\nreturn an error, which can be viewed in the controller logs and the object's events.\n\n### Precedence & Validity\n\nObjects with `.spec.insecure` as `true` will only be allowed if HTTP connections are allowed at the controller level.\nSimilarly, an object can have `.spec.insecure` as `true` only if the Saas/Cloud provider allows HTTP connections.\nFor example, using a `Bucket` with its `.spec.provider` set to `azure` would be invalid since Azure doesn't allow\nHTTP connections.\n\n### User Stories\n\n#### Story 1\n\n> As a cluster admin of a multi-tenant cluster, I want to ensure all controllers access endpoints using only HTTPS\n> regardless of tenants' object definitions.\n\nApply a `kustomize` patch which prevents the use of HTTP connections:\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n  - gotk-components.yaml\n  - gotk-sync.yaml\npatches:\n  - patch: |\n      - op: add\n        path: /spec/template/spec/containers/0/args/-\n        value: --insecure-allow-http=false\n    target:\n      kind: Deployment\n      name: \"(source-controller|notification-controller|image-reflector-controller|image-automation-controller)\"\n  # Since the above flag is not available in kustomize-controller for reasons explained in a previous section,\n  # we disable Kustomize remote builds by disallowing use of remote bases. This ensures that kustomize-controller\n  # won't initiate any plain HTTP connections.\n  - patch: |\n      - op: add\n        path: /spec/template/spec/containers/0/args/-\n        value: --no-remote-bases=true\n    target:\n      kind: Deployment\n      name: kustomize-controller\n```\n\n#### Story 2\n\n> As an application developer, I'm trying to debug a new image pushed to my local registry which\n> is not served over HTTPS.\n\nModify the object spec to use HTTP connections explicitly:\n```yaml\napiVersion: image.toolkit.fluxcd.io/v1beta1\nkind: ImageRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  image: kind-registry:5000/stefanprodan/podinfo\n  interval: 1m0s\n  insecure: true\n```\n\n### Alternatives\n\nInstead of adding a flag, we can instruct users to make use of Kyverno policies to enforce that\nall objects have `.spec.insecure` as `false` and any URLs present in the definition don't have `http`\nas the scheme. This is less attractive, as this would ask users to install another software and prevent\nFlux multi-tenancy from being standalone.\n\n## Design Details\n\nIf a controller is started with `--insecure-allow-http=false`, any URL in a Flux object which has `http`\nas the scheme will result in an unsuccessful reconciliation and the following condition will be added to the object's\n`.status.conditions`:\n\n```yaml\nstatus:\n  conditions:\n  - lastTransitionTime: \"2022-09-06T09:14:21Z\"\n    message: \"Use of insecure HTTP connections isn't allowed for this controller\"\n    observedGeneration: 1\n    reason: InsecureConnectionsDisallowed\n    status: \"True\"\n    type: Stalled\n```\n\nSimilarly, if an object has `.spec.insecure` as `true` but the Cloud provider doesn't allow HTTP connections,\nthe reconciliation will fail and the following condition will be added to the object's `.status.conditions`:\n\n```yaml\nstatus:\n  conditions:\n  - lastTransitionTime: \"2022-09-06T09:14:21Z\"\n    message: \"Use of insecure HTTP connections isn't allowed for Azure Storage\"\n    observedGeneration: 1\n    reason: UnsupportedConnectionType\n    status: \"True\"\n    type: Stalled\n```\n\nIf an object has `.spec.insecure` as `true`, the registry client or bucket client shall be created with the use\nof HTTP connections enabled explicitly.\n\n## Implementation History\n\n**2022-08-12** Allow defining OCI sources for non-TLS container registries with `flux create source oci --insecure`\nreleased with [flux2 v0.34.0](https://github.com/fluxcd/flux2/releases/tag/v0.34.0)\n\n"
  },
  {
    "path": "rfcs/0005-artifact-revision-and-digest/README.md",
    "content": "# RFC-0005 Artifact `Revision` format and introduction of `Digest`\n\n**Status:** implemented\n\n**Creation date:** 2022-10-20\n\n**Last update:** 2023-02-20\n\n## Summary\n\nThis RFC proposes to establish a canonical `Revision` format for an `Artifact`\nwhich points to a specific revision represented as a checksum (e.g. an OCI\nmanifest digest or Git commit SHA) of a named pointer (e.g. an OCI repository\nname or Git tag). In addition, it proposes to include the algorithm name (e.g.\n`sha256`) as a prefix to an advertised checksum for an `Artifact` and\nfurther referring to it as a `Digest`, deprecating the `Checksum` field.\n\n## Motivation\n\nThe current `Artifact` type's `Revision` field format is not \"officially\"\nstandardized (albeit assumed throughout our code bases), and has mostly been\nderived from `GitRepository` which uses `/` as a separator between the named\npointer (a Git branch or tag) and a specific (SHA-1, or theoretical SHA-256)\nrevision.\n\nSince the introduction of `OCIRepository` and with the recent changes around\n`HelmChart` objects to allow the consumption of charts from OCI registries,\nthis could be seen as outdated or confusing due to the format differing from\nthe canonical format used by OCI, which is `<name>@<algo>:<checksum>` (the\npart after `@` formally known as a [\"digest\"][digest-spec]) to refer to a\nspecific version of an OCI manifest.\n\nWhile also taking note that Git does not have an official canonical format for\ne.g. branch references at a specific commit, and `/` has less of a symbolic\nmeaning than `@`, which could be interpreted as \"`<branch>` _at_\n`<commit SHA>`\".\n\nIn addition, with the introduction of algorithm prefixes for an `Artifact`'s\nchecksum, it would be possible to add support and allow user configuration of\nother algorithms than SHA-256. For example SHA-384 and SHA-512, or the more\nperformant (parallelizable) [BLAKE3][].\n\nBesides this, it would make it easier to implement a client that can verify the\nchecksum without having to resort to an assumed format or guessing\nmethod based on the length of it, and allows for a more robust solution in\nwhich it can continue to calculate against the algorithm of a previous\nconfiguration.\n\nThe inclusion of the `Artifact`'s algorithm prefix has been proposed before in\n[source-controller#855](https://github.com/fluxcd/source-controller/issues/855),\nwith supportive response from Core Maintainers.\n\n### Goals\n\n- Establish a canonical format to refer to an `Artifact`'s `Revision` field\n  which consists of a named pointer and a specific checksum reference.\n- Allow easier verification of the `Artifact`'s checksum by including an\n  alias for the algorithm.\n- Deprecate the `Artifact`'s `Checksum` field in favor of the `Digest` field.\n- Allow configuration of the algorithm used to calculate the checksum of an\n  `Artifact`.\n- Allow configuration of algorithms other than SHA-256 to calculate the\n  `Digest` of an `Artifact`.\n- Allow compatibility with SemVer name references which might contain an `@`\n  symbol already (e.g. `package@v1.0.0@sha256:...`, as opposed to OCI's\n  `name:v1.0.0@sha256:...`).\n\n### Non-Goals\n\n- Define a canonical format for an `Artifact`'s `Revision` field which contains\n  a named pointer and a different reference than a checksum.\n\n## Proposal\n\n### Establish an Artifact Revision format\n\nChange the format of the `Revision` field of the `source.toolkit.fluxcd.io`\nGroup's `Artifact` type across all `Source` kinds to contain an `@` separator\nopposed to `/`, and include the algorithm alias as a prefix to the checksum\n(creating a \"digest\").\n\n```text\n[ <named pointer> ] [ [ \"@\" ] <algo> \":\" <checksum> ]\n```\n\nWhere `<named pointer>` is the name of e.g. a Git branch or OCI repository\nname, `<checksum>` is the exact revision (e.g. a Git commit SHA or OCI manifest\ndigest), and `<algo>` is the alias of the algorithm used to calculate the\nchecksum (e.g. `sha256`). In case only a named pointer or digest is advertised,\nthe `@` is omitted.\n\nFor a `GitRepository`'s `Artifact` pointing towards an SHA-1 Git commit on\nbranch `main`, the `Revision` field value would become:\n\n```text\nmain@sha1:1eabc9a41ca088515cab83f1cce49eb43e84b67f\n```\n\nFor a `GitRepository`'s `Artifact` pointing towards a specific SHA-1 Git commit\nwithout a defined branch or tag, the `Revision` field value would become:\n\n```text\nsha1:1eabc9a41ca088515cab83f1cce49eb43e84b67f\n```\n\nFor a `Bucket`'s `Artifact` with a revision based on an SHA-256 calculation of\na list of object keys and their etags, the `Revision` field value would become:\n\n```text\nsha256:8fb62a09c9e48ace5463bf940dc15e85f525be4f230e223bbceef6e13024110c\n```\n\nFor a `HelmChart`'s `Artifact` pointing towards a Helm chart version, the\n`Revision` field value would become:\n\n```text\n1.2.3\n```\n\n### Introduce a `Digest` field\n\nIntroduce a new field to the `source.toolkit.fluxcd.io` Group's `Artifact` type\nacross all `Source` kinds called `Digest`, containing the checksum of the file\nadvertised in the `Path`, and alias of the algorithm used to calculate it\n(creating a [\"digest\"][digest-spec]).\n\n```text\n<algo> \":\" <checksum>\n```\n\nFor a `GitRepository` `Artifact`'s checksum calculated using SHA-256, the\n`Digest` field value would become:\n\n```text\nsha256:1111f92aba67995f108b3ee3ffdc00edcfe206b11fbbb459c8ef4c4a8209fca8\n```\n\n#### Deprecate the `Checksum` field\n\nIn favor of the `Digest` field, the `Checksum` field of the `source.toolkit.fluxcd.io`\nGroup's `Artifact` type across all `Source` kinds is deprecated, and removed in\na future version.\n\n### User Stories\n\n#### Artifact revision verification\n\n> As a user of the source-controller, I want to be able to see the exact\n> revision of an Artifact that is being used, so that I can verify that it\n> matches the expected revision at a remote source.\n\nFor a Source kind that has an `Artifact` with a `Revision` which contains a\nchecksum, the field value can be retrieved using the Kubernetes API. For\nexample:\n\n```console\n$ kubectl get gitrepository -o jsonpath='{.status.artifact.revision}' <name>\nmain@sha1:1eabc9a41ca088515cab83f1cce49eb43e84b67f\n```\n\n#### Artifact checksum verification\n\n> As a user of the source-controller, I want to be able to verify the checksum\n> of an Artifact.\n\nFor a Source kind with an `Artifact` the digest consisting of the algorithm\nalias and checksum is advertised in the `Digest` field, and can be retrieved\nusing the Kubernetes API. For example:\n\n```console\n$ kubectl get gitrepository -o jsonpath='{.status.artifact.digest}' <name>\nsha256:1111f92aba67995f108b3ee3ffdc00edcfe206b11fbbb459c8ef4c4a8209fca8\n```\n\n#### Artifact checksum algorithm configuration\n\n> As a user of the source-controller, I want to be able to configure the\n> algorithm used to calculate the checksum of an Artifact.\n\nThe source-controller binary accepts a `--artifact-digest-algo` flag which\nconfigures the algorithm used to calculate the checksum of an `Artifact`.\nThe default value is `sha256`, but can be changed to `sha384`, `sha512`\nor `blake3`.\n\nWhen set, newly advertised `Artifact`'s `Digest` fields will be calculated\nusing the configured algorithm. For previous `Artifact`'s that were set using\na previous configuration, the `Artifact`'s `Digest` field will be recalculated\nusing the advertised algorithm.\n\n#### Artifact revisions in notifications\n\n> As a user of the notification-controller, I want to be able to see the\n> exact revision a notification is referring to.\n\nThe notification-controller can use the revision for a Source's `Artifact`\nattached as an annotation to an `Event`, and correctly parses the value field\nwhen attempting to extract e.g. a Git commit digest from an event for a\n`GitRepository`. As currently already applicable for the `/` separator.\n\n> As a user of the notification-controller, I want to be able to observe what\n> commit has been applied on my (supported) Git provider.\n\nThe notification-controller can use the revision attached as an annotation to\nan `Event`, and is capable of extracting the correct reference for a Git\nprovider integration (e.g. GitHub, GitLab) to construct a payload. For example,\nextracting `1eabc9a41ca088515cab83f1cce49eb43e84b67f` from\n`main@sha1:1eabc9a41ca088515cab83f1cce49eb43e84b67f`.\n\n#### Artifact revisions in listed views\n\n> As a Flux CLI user, I want to see the current revision of my Source in a\n> listed overview.\n\nBy running `flux get source <kind>`, the listed view of Sources would show a\ntruncated version of the checksum in the `Revision` field.\n\n```console\n$ flux get source gitrepository\nNAME            REVISION              SUSPENDED       READY   MESSAGE                                                                      \nflux-monitoring main@sha1:1eabc9a4    False           True    stored artifact for revision 'main@sha1:1eabc9a41ca088515cab83f1cce49eb43e84b67f'\n\n$ flux get source oci\nNAME            REVISION               SUSPENDED       READY   MESSAGE                                                                                              \napps-source     local@sha256:e5fa481b  False           True    stored artifact for digest 'local@sha256:e5fa481bb17327bd269927d0a223862d243d76c89fe697ea8c9adefc47c47e17'\n\n$ flux get source bucket\nNAME            REVISION         SUSPENDED       READY   MESSAGE                                                                                              \napps-source     sha256:e3b0c442  False           True    stored artifact for revision 'sha256:8fb62a09c9e48ace5463bf940dc15e85f525be4f230e223bbceef6e13024110c'\n```\n\n### Alternatives\n\nThe two main alternatives around the `Revision` parts in this RFC are to either\nkeep the current field value formats as is, or to invent another format. Given\nthe [motivation](#motivation) for this RFC outlines the reasoning for not\nkeeping the current `Revision` format, and the proposed is a commonly known\nformat. Neither strike as a better alternative.\n\nFor the changes related to `Checksum` and `Digest`, the alternative is to keep\nthe current field name as is, and only change the field value format. However,\ngiven the naming of the field is more accurate with the introduction of the\nalgorithm alias, and now is the time to make last (breaking) changes to the\nAPI. This does not strike as a better alternative.\n\n## Design Details\n\n### Artifact Revision format\n\nFor an `Artifact`'s `Revision` which contains a checksum referring to an exact\nrevision, the checksum within the value MUST be appended with an alias for the\nalgorithm separated by `:` (e.g. `sha256:...`), further referred to as a\n\"digest\". The algorithm alias and checksum of the digest MUST be lowercase and\nalphanumeric.\n\nFor an `Artifact`'s `Revision` which contains a digest and a named pointer,\nit MUST be prefixed with `@`, and appended at the end of the `Revision` value.\nThe named pointer MAY contain arbitrary characters, including but not limited\nto `/` and `@`.\n\n#### Format\n\n```text\n[ <named pointer> ] [ [ \"@\" ] <algo> \":\" <checksum> ]\n```\n\nWhere `[ ]` indicates an optional element, `\" \"` a literal string, and `< >`\na variable.\n\n#### Parsing\n\nWhen parsing the `Revision` field value of an `Artifact` to extract the digest,\nthe value after the last `@` is considered to be the digest. The remaining\nvalue on the left side is considered to be the named pointer, which MAY contain\nan additional `@` separator if applicable for the domain of the `Source`\nimplementation.\n\n#### Truncation\n\nWhen truncating the `Revision` field value of an `Artifact` to display in a\nview with limited space, the `<checksum>` of the digest MAY be truncated to\n7 or more characters. The `<algo>` of the digest MUST NOT be truncated.\nIn addition, a digest MUST always contain the full length checksum for the\nalgorithm.\n\n#### Backwards compatibility\n\nTo allow backwards compatibility in the notification-controller, Flux CLI and\nother applicable components, the `Revision` new field value format could be\ndetected by the presence of the `@` or `:` characters. Falling back to their\ncurrent behaviour if not present, phasing out the old format in a future\nrelease.\n\n### Artifact Digest\n\nThe `Artifact`'s `Digest` field advertises the checksum of the file in the\n`URL`. The checksum within the value MUST be appended with an alias for the\nalgorithm separated by `:` (e.g. `sha256:...`). This follows the\n[digest format][go-digest] of OCI.\n\n#### Format\n\n```text\n<algo> \":\" <checksum>\n```\n\nWhere `\" \"` indicates a literal string, and `< >` a variable.\n\n#### Library\n\nThe library used for calculating the `Digest` field value is\n`github.com/opencontainers/go-digest`. This library is stable and extensible,\nand used by various OCI libraries which we already depend on.\n\n#### Calculation\n\nThe checksum in the `Digest` field value MUST be calculated using the canonical\nalgorithm [set at runtime](#configuration).\n\n#### Configuration\n\nThe algorithm used for calculating the `Digest` field value MAY be configured\nusing the `--artifact-digest-algo` flag of the source-controller binary. The\ndefault value is `sha256`, but can be changed to `sha384`, `sha512` or\n`blake3`.\n\n**Note:** availability of BLAKE3 is at present dependent on an explicit import\nof `github.com/opencontainers/go-digest/blake3`.\n\nWhen the provided algorithm is NOT supported, the source-controller MUST\nfail to start.\n\nWhen the configured algorithm changes, the `Digest` MAY be recalculated to\nupdate the value.\n\n#### Verification\n\nThe checksum of a downloaded artifact MUST be verified against the `Digest`\nfield value. If the checksum does not match, the verification MUST fail.\n\n### Deprecation of Checksum\n\nThe `Artifact`'s `Checksum` field is deprecated and MUST be removed in a\nfuture release. The `Digest` field MUST be used instead.\n\n#### Backwards compatibility\n\nTo allow backwards compatibility, the source-controller could continue\nto advertise the checksum part of a `Digest` in the `Checksum` field until\nthe field is removed.\n\n## Implementation History\n\n* **2023-02-20** First implementation released with [flux2 v0.40.0](https://github.com/fluxcd/flux2/releases/tag/v0.40.0)\n\n[BLAKE3]: https://github.com/BLAKE3-team/BLAKE3\n[digest-spec]: https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests\n[go-digest]: https://pkg.go.dev/github.com/opencontainers/go-digest#hdr-Basics"
  },
  {
    "path": "rfcs/0006-cdevents/README.md",
    "content": "# RFC-0006 Flux CDEvents Receiver\n\n**Status:** implementable\n\n<!--\nStatus represents the current state of the RFC.\nMust be one of `provisional`, `implementable`, `implemented`, `deferred`, `rejected`, `withdrawn`, or `replaced`.\n-->\n\n**Creation date:** 2023-12-08\n\n**Last update:** 2024-03-13\n\n## Summary\n\nThis RFC proposes to add a `Receiver` type to the Flux notification-controller API\nfor handling [CDEvents](https://cdevents.dev/).\n\nFor `Receiver` objects configured to accept CDEvents,\nnotification-controller will verify the events sent to the receiver's webhook URL,\ncheck that their type matches the expected type, and trigger the reconciliation\nof the configured resources.\n\n## Motivation\n\nCDEvents enables interoperability between CI/CD tools in a workflow, and Flux is a\nvery popular continuous delivery tool, and consequently the CDF team received many questions\nabout integrating CDEvents with Flux. \n\n### Goals\n\nAllow Flux to receive CDEvents and trigger the reconciliation of resources based on the received events.\n\n### Non-Goals\n\nMake the Flux controllers emit CDEvents.\n\n## Proposal\n\nAdd CDEvents to the list of available receivers in Flux notification-controller.\nSimilar to other receivers such as GitHub, Flux users will be able to use `spec.events`\nin order to specify which event types the receiver will allow.\nThe receiver will also verify using the [CDEvents Go SDK](https://github.com/cdevents/sdk-go) that the\npayload sent to the webhook URL is a valid CDEvent.\n\n### User Stories\n\nUsers of multiple CI/CD tools such as Tekton and Flux\ncould use CDEvents as a way to enable interoperability.\n\nFor example, a user may want a Flux resource to reconcile as part of a Tekton `pipeline`.\nThe Tekton `pipeline` will fire off a CDEvent to the CloudEvents Broker.\nA subscription that the user will have set up externally, e.g. with the [knative broker](https://knative.dev/docs/eventing/brokers/), will then\nsend a relevant CDEvent to the Flux webhook receiver endpoint. \n\n![usecase](cdevents-flux-tekton.png)\n\n### Alternatives\n\nCertain use cases for CDEvents could be done alternatively using\navailable receivers such as the generic webhook.\n\n## Design Details\n\nAdding a Flux `Receiver` for CDEvents that works much like the other event-based receivers already implemented.\n\nThe user will be able to define a Flux `Receiver` custom resource and deploy it to their cluster.\nThe receiver takes the payload sent to the webhook URL by an external events broker,\nchecks the headers for the event type, and filters out events based on the user-defined\nlist of events in `spec.events`. If left empty, it will act on all valid CDEvents.\nIt then validates the payload body using the [CDEvents Go SDK](https://github.com/cdevents/sdk-go).\nValid events will then trigger the reconciliation of all Flux objects specified in `.spec.resources`.\n\nThe CDEvents broker is not a part of this design and is left to the users to set up however they wish.\n\nExample Receiver:\n\n```yaml\napiVersion: notification.toolkit.fluxcd.io/v1\nkind: Receiver\nmetadata:\n  name: cdevents-receiver\n  namespace: flux-system\nspec:\n  type: cdevents\n  events:\n    - \"dev.cdevents.change.merged\"\n  secretRef:\n    name: receiver-token\n  resources:\n    - apiVersion: source.toolkit.fluxcd.io/v1\n      kind: GitRepository\n      name: webapp\n      namespace: flux-system\n```\n\n![User Flowchart](Flux-CDEvents-RFC.png)\n\n![Adapter](CDEvents-Flux-RFC-Adapter.png)\n\n\n## Implementation History\n\n<!--\nMajor milestones in the lifecycle of the RFC such as:\n- The first Flux release where an initial version of the RFC was available.\n- The version of Flux where the RFC graduated to general availability.\n- The version of Flux where the RFC was retired or superseded.\n-->\n"
  },
  {
    "path": "rfcs/0007-git-repo-passwordless-auth/README.md",
    "content": "# RFC-0007 Passwordless authentication for Git repositories\n\n**Status:** implementable\n\n**Creation date:** 2023-31-07\n**Last update:** 2025-08-13\n\n## Summary\n\nFlux should provide a mechanism to authenticate against Git repositories without\nthe use of passwords. This RFC proposes the use of alternative authentication\nmethods like OIDC, OAuth2 and IAM to access Git repositories hosted on specific\nGit SaaS platforms and cloud providers.\n\n## Motivation\n\nAt the moment, Flux supports HTTP basic and bearer authentication. Users are\nrequired to create a Secret containing the username and the password/bearer\ntoken, which is then referred to in the GitRepository using `.spec.secretRef`.\n\nWhile this works fine, it has a couple of drawbacks:\n* Scalability: Each new GitRepository potentially warrants another credentials\npair, which doesn't scale well in big organizations with hundreds of\nrepositories with different owners, increasing the risk of mismanagement and\nleaks.\n* Identity: A username is associated with an actual human. But often, the\nrepository belongs to a team of 2 or more people. This leads to a problem where\nteams have to decide whose credentials should Flux use for authentication.\n\nThese problems exist not due to flaws in Flux, but because of the inherent\nnature of password based authentication.\n\nWith support for OIDC, OAuth2 and IAM based authentication, we can eliminate\nthese problems:\n* Scalability: Since OIDC is fully handled by the cloud provider, it eliminates\nany user involvement in managing credentials. For OAuth2 and IAM, users do need\nto provide certain information like the ID of the resource, private key, etc.\nbut these are still a better alternative to passwords since the same resource\ncan be reused by multiple teams with different members.\n* Identity: Since all the above authentication methods are associated with a\nvirtual resource independent of a user, it solves the problem of a single person\nbeing tied to automation that several people are involved in.\n\n### Goals\n\n* Integrate with major cloud providers' OIDC and IAM offerings to provide a\nseamless way of Git repository authentication.\n* Integrate with major Git SaaS providers to support their app based OAuth2\nmechanism.\n\n### Non-Goals\n\n* Replace the existing basic and bearer authentication API.\n\n## Proposal\n\nA new string field `.spec.provider` shall be added to the `GitRepository` API.\nThe field will be an enum with the following variants:\n\n* `generic`\n* `azure`\n* `gcp`\n* `github`\n* `gitlab`\n\n`.spec.provider` will be an optional field which defaults to `generic` indicating\nthat the user wants to authenticate via HTTP basic/bearer auth or SSH by providing\nthe existing `.spec.secretRef` field. The sections below define the behavior when\n`.spec.provider` is set to one of the other providers.\n\n### Azure\n\nGit repositories hosted on Azure Devops can be accessed using [managed\nidentity](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops).\nSeamless access from Flux to Azure devops repository can be achieved through\n[Workload\nIdentity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview?tabs=dotnet).\nThe user creates a managed identity and establishes a federated identity between\nFlux service account and the managed identity. Flux service account is patched\nto add an annotation specifying the client id of the managed identity. Flux\nservice account and deployments are patched with labels to use workload\nidentity.  The managed identity must have sufficient permissions to be able to\naccess Azure Devops resources. This enables Flux pod to access the Git\nrepository without the need for any credentials.\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n  - gotk-components.yaml\n  - gotk-sync.yaml\npatches:\n  - patch: |-\n      apiVersion: v1\n      kind: ServiceAccount\n      metadata:\n        name: source-controller\n        namespace: flux-system\n        annotations:\n          azure.workload.identity/client-id: <AZURE_CLIENT_ID>\n        labels:\n          azure.workload.identity/use: \"true\"      \n  - patch: |-\n      apiVersion: apps/v1\n      kind: Deployment\n      metadata:\n        name: source-controller\n        namespace: flux-system\n        labels:\n          azure.workload.identity/use: \"true\"\n      spec:\n        template:\n          metadata:\n            labels:\n              azure.workload.identity/use: \"true\" \n```\n\nExample of using an Azure Devops repository with `azure` provider:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: azure-devops\nspec:\n  interval: 1m\n  url: https://dev.azure.com/<org>/<project>/_git/<repository>\n  ref:\n    branch: master\n  # notice the lack of secretRef\n  provider: azure\n```\n\n### GCP\n\nGit repositories hosted on Google Cloud Source Repositories can be accessed by\nFlux via a [GCP Service Account](https://cloud.google.com/iam/docs/service-account-overview).\n\nWorkload Identity Federation for GKE is [unsupported](https://cloud.google.com/iam/docs/federated-identity-supported-services)\nfor Cloud Source Repositories. The user must instead create the GCP Service Account and \nlink it to the Flux service account in order to enable workload identity.\n\nIn order to link the GCP Service Account to the Flux service\naccount, the following patch must be applied:\n  \n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n  - gotk-components.yaml\n  - gotk-sync.yaml\npatches:\n  - patch: |\n      apiVersion: v1\n      kind: ServiceAccount\n      metadata:\n        name: source-controller\n        annotations:\n          iam.gke.io/gcp-service-account: <identity-name>      \n    target:\n      kind: ServiceAccount\n      name: source-controller\n```\n\nThe Service Account must have sufficient permissions to be able to access Google\nCloud Source Repositories. The Cloud Source Repositories uses the `source.repos.get`\npermission to access the repository, which is under the `roles/source.reader` role.\nTake a look at [this guide](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)\nfor more information about setting up GKE Workload Identity.\n\nExample of using a Google Cloud Source Repository with `gcp` provider:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: gcp-repo\nspec:\n  interval: 1m\n  url: https://source.developers.google.com/p/<project>/r/<repository>\n  ref:\n    branch: master\n  provider: gcp\n```\n\n### GitHub\n\nGit repositories hosted on GitHub can be accessed via [GitHub Apps](https://docs.github.com/en/apps/overview).\nThis allows users to create a single resource from which they can access all\ntheir GitHub repositories. The app must have sufficient permissions to be able\nto access repositories. The app's ID, private key and installation ID should\nbe mentioned in the Secret referred to by `.spec.secretRef`. GitHub Enterprise\nusers will also need to mention their GitHub API URL in the Secret.\n\nExample of using a Github repository with `github` provider:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: github-repo\nspec:\n  interval: 1m\n  url: https://github.com/<org>/<repository>\n  ref:\n    branch: master\n  provider: github\n  secretRef:\n    name: github-app\n---\nkind: Secret\nmetadata:\n  name: github-app\nstringData:\n  githubAppID: <app-id>\n  githubInstallationID: <installation-id>\n  githubPrivateKey: |\n    <PEM-private-key>\n  githubApiURl: <github-enterprise-api-url> #optional, required only for GitHub Enterprise users\n```\n\n### Gitlab\n\nGit repositories hosted on Gitlab can be accessed via OAuth2 Gitlab Applications\ncreated from the\n[UI](https://docs.gitlab.com/ee/integration/oauth_provider.html) or using\n[API](https://docs.gitlab.com/ee/api/applications.html). The Gitlab Oauth2\napplication must be created with the required scope to access gitlab\nrepositories. The application's `application_id`, `secret` and `redirect_uri`\nare used to request an [access\ntoken](https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow).\nThese parameters are configured in the secret referred to by `.spec.secretRef`.\n\nExample of using gitlab repository with `gitlab` provider:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: gitlab-repo\nspec:\n  interval: 1m\n  url: https://gitlab.com/<org>/<repository>\n  ref:\n    branch: main\n  provider: gitlab\n  secretRef:\n    name: gitlab-app\n---\nkind: Secret\nmetadata:\n  name: gitlab-app\nstringData:\n  gitlabAppID: <app-id>\n  gitlabAppSecret: <app-secret>\n  gitlabAppRedirectUrl: <app-redirect-url>\n```\n\n### User Stories \n\n#### User Story 1\n\n> As a user running flux controllers, deployed from a private repository in\n> a cloud provider that supports context-based authentication, I want to securely\n> authenticate to the repository without setting up secrets and having to manage\n> authentication tokens (refreshing, rotating, etc.).\n\nTo enable this scenario, the user would enable context-based authentication in\ntheir cloud provider and integrate it with their kubernetes cluster. For example,\nin Azure, using AKS and Azure Devops, the user would create a managed identity and\nestablish a federated identity between Flux service account and the managed identity.\nFlux would then be able to access the Git repository by requesting a token from the\nAzure service. The user would not need to create a secret or manage any tokens.\n\n#### User Story 2\n\n> As a user running flux controllers, deployed from a private repository, I want\n> to configure authentication to the repository that is not associated to a\n> personal account and does not expire. \n\nTo enable this scenario, the user would either enable context-based authentication\nin their cloud provider and integrate it with their kubernetes cluster, or set \nup an OAuth2 application in their Git SaaS provider and provide the OAuth2 application\ndetails (application ID, secret, redirect URL) in a kubernetes secret. \nFlux would then be able to access the Git repository by requesting a token from the\ncloud provider or Git SaaS provider. The user would not need to create any credentials\ntied to a personal account.\n\n## Design Details\n\nFlux source controller uses `GitRepository` API to define a source to produce an\nArtifact for a Git repository revision. Flux image automation controller updates\nYAML files when new images are available and commits changes to a given Git\nrepository. The `ImageUpdateAutomation` API defines an automation process that\nupdates the Git repository referenced in it's `.spec.sourceRef`. If the new\noptional string field `.spec.provider` is specified in the `GitRepository` API,\nthe respective provider is used to configure the authentication to check out the\nsource for flux controllers.\n\n### Azure\n\nIf `.spec.provider` is set to `azure`, Flux controllers will use\n[DefaultAzureCredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#DefaultAzureCredential)\nto build the workload identity credential. This credential type uses the\nenvironment variables injected by the Azure Workload Identity mutating webhook.\nThe [access token from the credential will be then used as a bearer\ntoken](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops#q-can-i-use-a-service-principal-to-do-git-operations-like-clone-a-repo)\nto perform HTTP bearer authentication.\n\n### GCP\n\nIf `.spec.provider` is set to `gcp`, Flux source controller will fetch the access token\nfrom the [GKE metadata server](https://cloud.google.com/kubernetes-engine/docs/concepts/workload-identity#metadata_server).\nThe GKE metadata server runs as a DaemonSet, with one Pod on every Linux node or\na native Windows service on every Windows node in the cluster. The metadata server\nintercepts HTTP requests to `http://metadata.google.internal`. \n\nThe source controller will use the url `http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token`\nto retrieve a token for the IAM service account that the Pod is configured to impersonate. \nThis access token will be then used to perform HTTP basic authentication.\n\n### GitHub\n\nIf `.spec.provider` is set to `github`, Flux controllers will get the app\ndetails from the specified Secret and use it to [generate an app installation\ntoken](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app).\nThis token is then used as the password and [`x-access-token` as the username](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app#choosing-permissions-for-git-access)\nto perform HTTP basic authentication.\n\n### Gitlab\n\nIf `.spec.provider` is set to `gitlab`, Flux controllers will use the\napplication_id, secret and redirect_url specified in `.spec.secret` to generate\nan access token. The git repository can then be accessed by specifying [oauth2\nas the username and the access token as the\npassword](https://docs.gitlab.com/ee/api/oauth2.html#access-git-over-https-with-access-token)\nto perform HTTP basic authentication.\n\n## Implementation History\n\n* GitHub App authentication implemented and generally available in Flux v2.5.\n* Azure DevOps authentication implemented and generally available in Flux v2.4."
  },
  {
    "path": "rfcs/0008-custom-event-metadata-from-annotations/README.md",
    "content": "# RFC-0008 Custom Event Metadata from Annotations\n\n**Status:** implemented\n\n<!--\nStatus represents the current state of the RFC.\nMust be one of `provisional`, `implementable`, `implemented`, `deferred`, `rejected`, `withdrawn`, or `replaced`.\n-->\n\n**Creation date:** 2024-05-23\n\n**Last update:** 2025-02-22\n\n## Summary\n\nFlux users often run into situations where they wish to send custom, static metadata fields defined\nin Flux objects on the events dispatched by the respective Flux controller to Kubernetes and\nnotification-controller. This proposal offers a solution for supporting those use cases uniformly\nacross all Flux controllers by sending the annotation keys in Flux objects that are prefixed with\nthe API Group `event.toolkit.fluxcd.io` followed by a slash, i.e. `event.toolkit.fluxcd.io/`.\n\nAfter the overall outcome of this RFC is implemented, Flux would have customization options for\nnotification metadata strong enough to eliminate the need for the `.spec.summary` field from the\nAlert API. During the discussion of this RFC the Flux team has decided to deprecate `.spec.summary`\nin favor of `.spec.eventMetadata.summary`, and to remove this field in the Flux release of Alert\nAPI v1 GA when it takes place.\n\n## Motivation\n\nThis RFC comes as a response to the need for adding custom metadata to events about Flux objects\nsent to notification providers. See specific user stories in the [User Stories](#user-stories) section.\n\n### Goals\n\nProvide a method for Flux users to embed custom/static metadata in their Flux objects\nand have that metadata propagated to the notification providers.\n\n### Non-Goals\n\nIn this proposal we **do not** aim to provide a method for Flux users to send etcd-indexed custom metadata\nfields from Flux objects in events to notification-controller, most specifically labels. By design an event\nalready contains enough identification information to locate the associated Flux object inside the cluster,\nwhich covers the use case of labels. Flux does not wish to incentivize practices that are impactful to clusters\nwithout a strong reason or benefit.\n\n## Proposal\n\nWhen sending events about Flux objects, we propose sending annotation keys prefixed with the well-defined\nAPI Group `event.toolkit.fluxcd.io` followed by a slash, i.e. prefixed with `event.toolkit.fluxcd.io/`, in\naddition to all the metadata that is already sent in the event.\n\n### User Stories\n\n#### Story 1\n\n> As a user, I want to embed Flux into my GitHub Workflow in a way that it only succeeds if\n> the deployment made by Flux is successful.\n\nFor example, embedding a Deployment ID from the GitHub API in a `HelmRelease` object like the one below:\n\n```yaml\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: podinfo\n  namespace: flux-system\n  annotations:\n    event.toolkit.fluxcd.io/deploymentID: e076e315-5a48-41c3-81c8-8d8bdee7d74d\nspec:\n  chart:\n    spec:\n      chart: podinfo\n      version: 6.5.*\n      sourceRef:\n        kind: HelmRepository\n        name: podinfo\n```\n\nShould cause notification-controller to propagate an event like the one below (most fields omitted for brevity):\n\n```json\n{\n  \"involvedObject\": {\n    \"apiVersion\": \"helm.toolkit.fluxcd.io/v2\",\n    \"kind\": \"HelmRelease\",\n    \"name\": \"podinfo\",\n    \"namespace\": \"flux-system\",\n    \"uid\": \"7d0cdc51-ddcf-4743-b223-83ca5c699632\"\n  },\n  \"metadata\": {\n    \"deploymentID\": \"e076e315-5a48-41c3-81c8-8d8bdee7d74d\"\n  }\n}\n```\n\n#### Story 2\n\n> As a user, I want to embed the new image tag in a `HelmRelease` object when the image is updated by an `ImageUpdateAutomation`\n> and have that information propagated to the notification providers.\n\nFor example:\n\n```yaml\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: podinfo\n  namespace: flux-system\n  annotations:\n    event.toolkit.fluxcd.io/image: ghcr.io/stefanprodan/podinfo:latest # {\"$imagepolicy\": \"flux-system:podinfo\"}\nspec:\n  chart:\n    spec:\n      chart: podinfo\n      sourceRef:\n        kind: HelmRepository\n        name: podinfo\n  values:\n    image:\n      tag: latest  # {\"$imagepolicy\": \"flux-system:podinfo:tag\"}\n```\n\nIn this example image-automation-controller would update the image and tag near the markers. If, for example, it\nupdates the image to `ghcr.io/stefanprodan/podinfo:6.5.0`, then it would cause notification-controller to start\npropagating events like the one below (most fields omitted for brevity):\n\n```json\n{\n  \"involvedObject\": {\n    \"apiVersion\": \"helm.toolkit.fluxcd.io/v2\",\n    \"kind\": \"HelmRelease\",\n    \"name\": \"podinfo\",\n    \"namespace\": \"flux-system\",\n    \"uid\": \"7d0cdc51-ddcf-4743-b223-83ca5c699632\"\n  },\n  \"metadata\": {\n    \"image\": \"ghcr.io/stefanprodan/podinfo:6.5.0\"\n  }\n}\n```\n\n### Alternatives\n\n#### Alternative 1\n\nAn alternative for specifying custom metadata fields in Flux objects for sending on events\nis defining `.spec` APIs for such, like `.spec.eventMetadata` available in the Alert API.\nThis alternative is not great because:\n\n* Such APIs would be fairly redundant with the well-known Kubernetes annotations.\n* Technically speaking, it is much easier to implement an alternative where the\nfield storing the custom metadata is the same and is already available across all the\nFlux objects rather than introducing a new API.\n\nIn the specific case of the Alert API this field was introduced because the Alert API\nis obviously a special one in the context of events and alerting. In particular, the\nAlert objects do not generate events themselves, but rather serve as an aggregation\nconfiguration for matching and propagating events from other Flux objects.\n\n#### Alternative 2\n\nInstead of introducing a new API Group, i.e. `event.toolkit.fluxcd.io`, we could use the API\nGroup `notification.toolkit.fluxcd.io` for the same purpose. This alternative is not great\nbecause it emphasizes an exclusive relationship with notification-controller, which is not the\ncase. The events here are also Kubernetes Events, and an API Group that is more general and\ncloser to Kubernetes Events is more appropriate.\n\n## Design Details\n\nAll the Flux controllers use our implementation of the `EventRecorder` interface from the Go\npackage `k8s.io/client-go/tools/record`: [`(*github.com/fluxcd/pkg/runtime/event.Recorder).AnnotatedEventf()`](https://github.com/fluxcd/pkg/blob/6f2619522699f1a78e8c7b41583ad9f7b7c9544e/runtime/events/recorder.go#L119).\nThis implementation sends the events to notification-controller and also calls the same method\nfrom an injected `EventRecorder`, which sends the events to Kubernetes. To support the use cases\ndiscussed here we would modify this implementation to look for annotations prefixed with\n`event.toolkit.fluxcd.io/` in the Flux objects and send them alongside the other metadata of\nthe event. Here we are talking specifically about the object annotations retrieved from the\nFlux object itself, i.e. the first argument of the `AnnotatedEventf()` method: `object runtime.Object`.\nThis implementation would not change the interface `EventRecorder` used by the controllers,\nso all we need to do is bump the Go package `github.com/fluxcd/pkg/runtime` across all controllers.\n\nOn the notification-controller side we would start accepting metadata keys starting with this\nprefix and remove it before sending the metadata key-value pair to the notification providers.\nThis is an important aspect of the implementation because notification-controller only\naccepts metadata keys that are prefixed with the Group of the respective API the involved\nFlux object belongs to, so we need to add an exception for the new prefix.\n\nThe API Group `event.toolkit.fluxcd.io` would be introduced as a constant in the package\n`github.com/fluxcd/pkg/apis/event` with the name `Group`. This constant would be used in the package\n`github.com/fluxcd/pkg/runtime/event` and notification-controller for the implementation described above.\n\n### Precedence Order\n\nAfter this change there would be four sources of metadata being sent on notifications.\nThey are listed below with the proposed order of precedence, from lowest to highest:\n\n1. User-defined metadata on Flux objects, set with the `event.toolkit.fluxcd.io/` prefix in the keys of the object's `.metadata.annotations`.\n2. User-defined metadata on the Alert object, set with `.spec.eventMetadata`.\n3. User-defined summary on the Alert object, set with `.spec.summary`.\n4. Controller-defined metadata, set with the `<controller group>.toolkit.fluxcd.io/` prefix in the metadata keys of the event payload.\n\nUpon any key conflicts when combining all these metadata, notification-controller would\nresolve them according to the precedence order specified above, print an `info` log and emit a\nKubernetes Event containing all the key conflicts to warn the user and prompt them to change\ntheir configuration to remove those conflicts.\n\n#### Reasoning\n\nController-defined metadata has the highest precedence because it integrates with external systems,\ne.g. commit SHAs, digests, chart versions, etc.\n\nAlert-level metadata, i.e. `.spec.summary` and `.spec.eventMetadata`, are usually cluster-level,\ne.g. the cluster name, region, environment, etc. We don't want tenants overriding cluster-level\nmetadata.\n\nUser-defined metadata on Flux objects, whose use cases are described in the [User Stories](#user-stories)\nsection, would usually be defined by cluster tenants. Hence it should not override cluster-level metadata.\n\nThe `.spec.summary` field of the Alert API was the first introduced for supporting user-defined\nmetadata. The value of this field is appended to the notification metadata with the key `summary`.\nLater, `.spec.eventMetadata` was introduced to enhance this capability by supporting structured\nuser-defined metadata, i.e. the ability to specify keys other than `summary`. The latter is a\ngeneralization of the former, and, given also the overall strong customization options offered\nby Flux for notification metadata after the changes introduced in this RFC, the Flux team has\ndecided to deprecate `.spec.summary` in favor of `.spec.eventMetadata.summary`, and to remove\nit in the Flux release of Alert API v1 GA when it takes place. Until then, we decided to keep\nthe current priority of the field, which is higher than `.spec.eventMetadata.summary`.\n\n### How can this feature be enabled / disabled?\n\nTo enable the feature, use the `event.toolkit.fluxcd.io/` prefix in Flux object annotations,\nfor example:\n\n* `event.toolkit.fluxcd.io/image: ghcr.io/stefanprodan/podinfo`\n* `event.toolkit.fluxcd.io/deploymentID: e076e315-5a48-41c3-81c8-8d8bdee7d74d`\n\nIt's important to notice that not all Flux objects emit events, e.g. Alert and Provider objects.\nFor a list of the Flux objects that emit events, see the kinds allowed on the\n`.spec.eventSources[].kind` field of the Alert API.\n\nTo disable the feature, do not use `event.toolkit.fluxcd.io/` as a prefix in Flux object annotations.\n\n## Implementation History\n\n* RFC implemented and generally available in Flux v2.5.\n\n<!--\nMajor milestones in the lifecycle of the RFC such as:\n- The first Flux release where an initial version of the RFC was available.\n- The version of Flux where the RFC graduated to general availability.\n- The version of Flux where the RFC was retired or superseded.\n-->\n"
  },
  {
    "path": "rfcs/0009-custom-health-checks/README.md",
    "content": "# RFC-0009 Custom Health Checks for Kustomization using Common Expression Language (CEL)\n\n**Status:** implemented\n\n**Creation date:** 2024-01-05\n\n**Last update:** 2025-02-22\n\n## Summary\n\nThis RFC proposes to extend the Flux `Kustomization` API with custom health checks for\ncustom resources using the Common Expression Language (CEL).\n\nIn order to provide flexibility, we propose to use CEL expressions for defining the\nconditions that need to be met in order to determine the health of a custom resource.\nWe will introduce a new field called `healthCheckExprs` in the `Kustomization` CRD\nwhich will be a list of CEL expressions for evaluating the status of a particular\nKubernetes resource kind.\n\n## Motivation\n\nFlux uses the `kstatus` library during the health check phase to compute owned\nresources status. This works just fine for all the Kubernetes core resources\nand custom resources that comply with the `kstatus` conventions.\n\nThere are cases where the status of a custom resource does not follow the\n`kstatus` conventions. For example, we might want to compute the status of a custom\nresource based on a condition other than `Ready`. This is the case for resources\nthat perform intermediary patching, like `Certificate` from cert-manager, where one\nshould look at the `Issuing` condition to know if the certificate is being issued or\nnot before looking at the `Ready` condition.\n\nWe need to provide a way for users to express the conditions that need to be\nmet in order to determine the health of a custom resource. We seek a solution\nflexible enough to cover all possible use cases that does not require changing\nthe Flux source code for each new CRD.\n\n### Goals\n\n- Provide a generic solution for users to customise the health check evaluation of custom resources.\n- Provide a space for the community to contribute custom health checks for popular custom resources.\n\n### Non-Goals\n\n- We do not plan to support custom health checks for Kubernetes core resources.\n\n## Proposal\n\n### Introduce a new field `HealthCheckExprs` in the `Kustomization` CRD\n\nThe `HealthCheckExprs` field will be a list of `CustomHealthCheck` objects.\nThe `CustomHealthCheck` object fields would be: `apiVersion`, `kind`, `inProgress`,\n`failed` and `current`.\n\nFor example, consider the following `Certificate` resource:\n\n```yaml\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: app-certificate\nspec:\n  commonName: cert-manager-tls\n  dnsNames:\n  - app.ns.svc.cluster.local\n  ipAddresses:\n  - x.x.x.x\n  isCA: true\n  issuerRef:\n    group: cert-manager.io\n    kind: ClusterIssuer\n    name: app-issuer\n  secretName: app-tls-certs\n  subject:\n    organizations:\n    - example.com\n```\n\nThis `Certificate` resource will transition through the following `conditions`:\n`Issuing` and `Ready`.\n\nIn order to compute the status of this resource, we need to look at both the `Issuing`\nand `Ready` conditions.\n\nThe Flux `Kustomization` object used to apply the `Certificate` will look like this:\n\n```yaml\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: certs\nspec:\n  interval: 5m\n  prune: true\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  path: ./certs\n  wait: true\n  healthCheckExprs:\n  - apiVersion: cert-manager.io/v1\n    kind: Certificate\n    inProgress: \"status.conditions.filter(e, e.type == 'Issuing').all(e, e.observedGeneration == metadata.generation && e.status == 'True')\"\n    failed: \"status.conditions.filter(e, e.type == 'Ready').all(e, e.observedGeneration == metadata.generation && e.status == 'False')\"\n    current: \"status.conditions.filter(e, e.type == 'Ready').all(e, e.observedGeneration == metadata.generation && e.status == 'True')\"\n```\n\nThe `.spec.healthCheckExprs` field contains an entry for the `Certificate` kind, its `apiVersion`,\nand the CEL expressions that need to be met in order to determine the health status of all custom resources\nof this kind reconciled by the Flux `Kustomization`.\n\n### Custom Health Check Library\n\nTo help users define custom health checks, we will provide on the [fluxcd.io](https://fluxcd.io)\nwebsite a library of custom health checks for popular custom resources.\n\nThe Flux community will be able to contribute to this library by submitting pull requests\nto the [fluxcd/website](https://github.com/fluxcd/website) repository.\n\n### User Stories\n\n#### Configure health checks for non-standard custom resources\n\n> As a Flux user, I want to be able to specify health checks for\n> custom resources that don't have a Ready condition, so that I can be notified\n> when the status of my resources transitions to a failed state based on the evaluation\n> of a different condition.\n\nUsing `.spec.healthCheckExprs`, Flux users have the ability to\nspecify the conditions that need to be met in order to determine the health of\na custom resource. This enables Flux to query any `.status` field,\nbesides the standard `Ready` condition, and evaluate it using a CEL expression.\n\nExample for `SealedSecret` which has a `Synced` condition:\n\n```yaml\n  - apiVersion: bitnami.com/v1alpha1\n    kind: SealedSecret\n    failed: \"status.conditions.filter(e, e.type == 'Synced').all(e, e.status == 'False')\"\n    current: \"status.conditions.filter(e, e.type == 'Synced').all(e, e.status == 'True')\"\n```\n\n#### Use Flux dependencies for Kubernetes ClusterAPI\n\n> As a Flux user, I want to be able to use Flux dependencies bases on the\n> readiness of ClusterAPI resources, so that I can ensure that my applications\n> are deployed only when the ClusterAPI resources are ready.\n\nThe ClusterAPI resources have a `Ready` condition, but this is set in the status\nafter the cluster is first created. Given this behavior, at creation time Flux\ncannot find any condition to evaluate the status of the ClusterAPI resources,\nthus it considers them as static resources which are always ready.\n\nUsing `.spec.healthCheckExprs`, Flux users can specify that the `Cluster`\nkind is expected to have a `Ready` condition which will force Flux into waiting\nfor the ClusterAPI resources status to be populated.\n\nExample for `Cluster`:\n\n```yaml\n  - apiVersion: cluster.x-k8s.io/v1beta1\n    kind: Cluster\n    failed: \"status.conditions.filter(e, e.type == 'Ready').all(e, e.status == 'False')\"\n    current: \"status.conditions.filter(e, e.type == 'Ready').all(e, e.status == 'True')\"\n```\n\n### Alternatives\n\nWe need an expression language that is flexible enough to cover all possible use\ncases, without having to change Flux source code for each new use case.\n\nAn alternative that has been considered was to use `CUE` instead of `CEL`.\n`CUE` lang is a more powerful expression language, but given the fact that\nKubernetes makes use of `CEL` for CRD validation and admission control,\nwe have decided to also use `CEL` in Flux in order to be consistent with\nthe Kubernetes ecosystem.\n\n## Design Details\n\n### Introduce a new field `HealthCheckExprs` in the `Kustomization` CRD\n\nThe `api/v1/kustomization_types.go` file will be updated to add the `HealthCheckExprs`\nfield to the `KustomizationSpec` struct.\n\n```go\ntype KustomizationSpec struct {\n\t// +optional\n\tHealthCheckExprs []CustomHealthCheck `json:\"healthCheckExprs,omitempty\"`\n}\n\ntype CustomHealthCheck struct {\n\t// APIVersion of the custom resource under evaluation.\n\t// +required\n\tAPIVersion string `json:\"apiVersion\"`\n\t// Kind of the custom resource under evaluation.\n\t// +required\n\tKind string `json:\"kind\"`\n\n\tHealthCheckExpressions `json:\",inline\"`\n}\n\ntype HealthCheckExpressions struct {\n\t// Current is the CEL expression that determines if the status\n\t// of the custom resource has reached the desired state.\n\t// +required\n\tCurrent string `json:\"current\"`\n\t// InProgress is the CEL expression that determines if the status\n\t// of the custom resource has not yet reached the desired state.\n\t// +optional\n\tInProgress string `json:\"inProgress,omitempty\"`\n\t// Failed is the CEL expression that determines if the status\n\t// of the custom resource has failed to reach the desired state.\n\t// +optional\n\tFailed string `json:\"failed,omitempty\"`\n}\n```\n\nIf a CEL expression evaluation results in an error, for example, looking for a field that does not exist,\nthe health check will fail. Users will be encouraged to test their expressions\nin the [CEL Playground](https://playcel.undistro.io/). Here is where the community-maintained\n[library](#custom-health-check-library) will be super useful as some of the expressions might be complex.\n\nThe evaluation logic will be as follows.\n\nFirst, we check if the custom resource has a `.status.observedGeneration` field, if it does\nwe compare it with the `.metadata.generation` field to determine if the custom resource is in\nprogress. We consider it in progress if these fields differ, and don't evaluate any of the\nexpressions if that's the case. However, if these fields are equal there's no immediate\nconclusion about the health of the custom resource, so we proceed with the evaluation.\n\nFor each of the `InProgress`, `Failed` and `Current` expressions, we evaluate the expressions\nthat are specified (`InProgress` and `Failed` are optional) in this specific order and return\nthe respective status of the first expression that evaluates to `true`. If none of the\nexpressions evaluate to `true`, we consider the custom resource in progress.\n\nWhen the `Failed` expression is not specified the controller will keep evaluating the\n`Current` expression until it returns `true`, and will give up after the timeout defined in the Kustomization's `spec.timeout` field is reached.\nUsers will be encouraged to provide a `Failed` expression to avoid stalling the reconciliation\nloop until the timeout is reached.\n\n### Introduce a generic custom status reader\n\nWe'll Introduce a `StatusReader` that will be used to compute the status\nof custom resources based on the `CEL` expressions provided in the `CustomHealthCheck`:\n\n```go\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine\"\n\t\"github.com/fluxcd/cli-utils/pkg/kstatus/polling/event\"\n\tkstatusreaders \"github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n)\n\ntype CELStatusReader struct {\n\tgenericStatusReader engine.StatusReader\n\tgvk                 schema.GroupVersionKind\n}\n\nfunc NewCELStatusReader(mapper meta.RESTMapper, gvk schema.GroupVersionKind,\n\texprs *kustomizev1.HealthCheckExpressions) engine.StatusReader {\n\n\tgenericStatusReader := kstatusreaders.NewGenericStatusReader(mapper, genericConditions(gvk.Kind, exprs))\n\treturn &CELStatusReader{\n\t\tgenericStatusReader: genericStatusReader,\n\t\tgvk:                 gvk,\n\t}\n}\n\nfunc (g *CELStatusReader) Supports(gk schema.GroupKind) bool {\n\treturn gk == g.gvk.GroupKind()\n}\n\nfunc (g *CELStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) {\n\treturn g.genericStatusReader.ReadStatus(ctx, reader, resource)\n}\n\nfunc (g *CELStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) {\n\treturn g.genericStatusReader.ReadStatusForObject(ctx, reader, resource)\n}\n```\n\nThe `genericConditions` function takes the set of `CEL` expressions and returns a\nfunction that takes an `Unstructured` object and returns a `status.Result` object.\n\n```go\nimport (\n\t\"github.com/fluxcd/cli-utils/pkg/kstatus/status\"\n\t\"github.com/fluxcd/pkg/runtime/cel\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\nfunc genericConditions(exprs *kustomizev1.HealthCheckExpressions) func(u *unstructured.Unstructured) (*status.Result, error) {\n\treturn func(u *unstructured.Unstructured) (*status.Result, error) {\n\t\tobj := u.UnstructuredContent()\n\n\t\t// if status.observedGeneration exists and differs from metadata.generation return status.InProgress\n\n\t\tfor _, e := range []struct{\n\t\t\texpr   string\n\t\t\tstatus status.Status\n\t\t}{\n\t\t\t{expr: exprs.InProgress, status: status.InProgress},\n\t\t\t{expr: exprs.Failed, status: status.Failed},\n\t\t\t{expr: exprs.Current, status: status.Current},\n\t\t} {\n\t\t\tif e.expr != \"\" {\n\t\t\t\tresult, err := cel.EvaluateBooleanExpr(e.expr, obj)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif result {\n\t\t\t\t\treturn &status.Result{Status: e.status}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn &status.Result{Status: status.InProgress}, nil\n\t}\n}\n```\n\nThe CEL status reader will be used by the `statusPoller` provided to the kustomize-controller `reconciler`\nto compute the status of the resources for the registered custom resources GVKs.\n\nWe will implement a `CEL` environment that will use the Kubernetes CEL library to evaluate the `CEL` expressions.\n\n## Implementation History\n\n* RFC implemented and generally available in Flux v2.5.\n"
  },
  {
    "path": "rfcs/0010-multi-tenant-workload-identity/README.md",
    "content": "# RFC-0010 Multi-Tenant Workload Identity\n\n**Status:** implemented\n\n**Creation date:** 2025-02-22\n\n**Last update:** 2026-03-13\n\n## Summary\n\nIn this RFC we aim to add support for multi-tenant workload identity in Flux,\ni.e. the ability to specify at the object-level which set of cloud provider\npermissions must be used for interacting with the respective cloud provider\non behalf of the reconciliation of the object. In this process, credentials\nmust be obtained automatically, i.e. this feature must not involve the use\nof secrets. This would be useful in a number of Flux APIs that need to\ninteract with cloud providers, spanning all the Flux controllers.\n\n### Multi-Tenancy Model\n\nIn the context of this RFC, multi-tenancy refers to the ability of a single\nFlux instance running inside a Kubernetes cluster to manage Flux objects\nbelonging to all the tenants in the cluster while still ensuring that each\ntenant has access only to their own resources according to the Least Privilege\nPrinciple. In this scenario a tenant is often a team inside an organization,\nso the reader can consider the\n[multi-team tenancy model](https://kubernetes.io/docs/concepts/security/multi-tenancy/#multiple-teams).\nEach team has their own namespaces, which are not shared with other teams.\n\n## Motivation\n\nFlux has strong multi-tenancy features. For example, the `Kustomization` and\n`HelmRelease` APIs support the field `spec.serviceAccountName` for specifying\nthe Kubernetes `ServiceAccount` to impersonate when interacting with the\nKubernetes API on behalf of a tenant, e.g. when applying resources. This\nallows tenants to be constrained under the Kubernetes RBAC permissions\ngranted to this `ServiceAccount`, and therefore have access only to the\nspecific subset of resources they should be allowed to use.\n\nBesides the Kubernetes API, Flux also interacts with cloud providers, e.g.\ncontainer registries, object storage, pub/sub services, etc. In these cases,\nFlux currently supports basically two modes of authentication:\n\n- *Secret-based multi-tenant authentication*: Objects have the field\n  `spec.secretRef` for specifying the Kubernetes `Secret` containing the\n  credentials to use when interacting with the cloud provider. This is\n  similar to the `spec.serviceAccountName` field, but for cloud providers.\n  The problem with this approach is that secrets are a security risk and\n  operational burden, as they must be managed and rotated.\n- *Workload-identity-based single-tenant authentication*: Flux offers\n  single-tenant workload identity support by configuring the `ServiceAccount`\n  of the Flux controllers to impersonate a cloud identity. This eliminates\n  the need for secrets, as the credentials are obtained automatically by\n  the cloud provider Go libraries used by the Flux controllers when they\n  are running inside the respective cloud environment. The problem with\n  this approach is that it is single-tenant, i.e. all objects are reconciled\n  using the same cloud identity, the one associated with the respective controller.\n\nFor delivering the high level of security and multi-tenancy support that\nFlux aims for, it is necessary to extend the workload identity support to\nbe multi-tenant. This means that each object must be able to specify which\ncloud identity must be impersonated when interacting with the cloud provider\non behalf of the reconciliation of the object. This would allow tenants to\nbe constrained under the cloud provider permissions granted to this identity,\nand therefore have access only to the specific subset of resources they are\nallowed to manage.\n\n### Goals\n\nProvide multi-tenant workload identity support in Flux, i.e. the ability to\nspecify at the object-level which cloud identity must be impersonated to\ninteract with the respective cloud provider on behalf of the reconciliation\nof the object, without the need for secrets.\n\n### Non-Goals\n\nIt's not a goal of this RFC to implement an identity provider for Flux.\nInstead, the goal is to leverage Kubernetes' built-in identity provider\ncapabilities, i.e. the Kubernetes `ServiceAccount` token issuer, to\nobtain short-lived access tokens for the cloud providers.\n\n## Proposal\n\nFor supporting multi-tenant workload identity at the object-level for the Flux APIs\nwe propose associating the Flux objects with Kubernetes `ServiceAccounts`. The\ncontroller would need to create a token for the `ServiceAccount` associated with\nthe object in the Kubernetes API, and then exchange it for a short-lived access\ntoken for the cloud provider. This would require the controller `ServiceAccount`\nto have RBAC permission to create tokens for any `ServiceAccounts` in the cluster.\n\n### User Stories\n\n#### Story 1\n\n> As a cluster administrator, I want to allow tenant A to pull OCI artifacts\n> from the Amazon ECR repository belonging to tenant A, but only from this\n> repository. At the same time, I want to allow tenant B to pull OCI artifacts\n> from the Amazon ECR repository belonging to tenant B, but only from this\n> repository.\n\nFor example, I would like to have the following configuration:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1beta2\nkind: OCIRepository\nmetadata:\n  name: tenant-a-repo\n  namespace: tenant-a\nspec:\n  ...\n  provider: aws\n  serviceAccountName: tenant-a-ecr-sa\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-a-ecr-sa\n  namespace: tenant-a\n  annotations:\n    eks.amazonaws.com/role-arn: arn:aws:iam::123456789123:role/tenant-a-ecr\n---\napiVersion: source.toolkit.fluxcd.io/v1beta2\nkind: OCIRepository\nmetadata:\n  name: tenant-b-repo\n  namespace: tenant-b\nspec:\n  ...\n  provider: aws\n  serviceAccountName: tenant-b-ecr-sa\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-b-ecr-sa\n  namespace: tenant-b\n  annotations:\n    eks.amazonaws.com/role-arn: arn:aws:iam::123456789123:role/tenant-b-ecr\n```\n\n#### Story 2\n\n> As a cluster administrator, I want to allow tenant A to pull and push to the Git\n> repository in Azure DevOps belonging to tenant A, but only this repository. At\n> the same time, I want to allow tenant B to pull and push to the Git repository\n> in Azure DevOps belonging to tenant B, but only this repository.\n\nFor example, I would like to have the following configuration:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: tenant-a-repo\n  namespace: tenant-a\nspec:\n  ...\n  provider: azure\n  serviceAccountName: tenant-a-azure-devops-sa\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-a-azure-devops-sa\n  namespace: tenant-a\n  annotations:\n    azure.workload.identity/client-id: d6e4fc00-c5b2-4a72-9f84-6a92e3f06b08 # client ID for my tenant A\n    azure.workload.identity/tenant-id: 72f988bf-86f1-41af-91ab-2d7cd011db47 # azure tenant for the cluster (optional, defaults to the env var AZURE_TENANT_ID set in the controller)\n---\napiVersion: image.toolkit.fluxcd.io/v1beta2\nkind: ImageUpdateAutomation\nmetadata:\n  name: tenant-a-image-update\n  namespace: tenant-a\nspec:\n  ...\n  sourceRef:\n    kind: GitRepository\n    name: tenant-a-repo\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: GitRepository\nmetadata:\n  name: tenant-b-repo\n  namespace: tenant-b\nspec:\n  ...\n  provider: azure\n  serviceAccountName: tenant-b-azure-devops-sa\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-b-azure-devops-sa\n  namespace: tenant-b\n  annotations:\n    azure.workload.identity/client-id: 4a7272f9-f186-41af-9f84-6a92e32d7cd0 # client ID for my tenant B\n    azure.workload.identity/tenant-id: 72f988bf-86f1-41af-91ab-2d7cd011db47 # azure tenant for the cluster (optional, defaults to the env var AZURE_TENANT_ID set in the controller)\n---\napiVersion: image.toolkit.fluxcd.io/v1beta2\nkind: ImageUpdateAutomation\nmetadata:\n  name: tenant-b-image-update\n  namespace: tenant-b\nspec:\n  ...\n  sourceRef:\n    kind: GitRepository\n    name: tenant-b-repo\n```\n\n#### Story 3\n\n> As a cluster administrator, I want to allow tenant A to pull manifests from\n> the GCS bucket belonging to tenant A, but only from this bucket. At the same\n> time, I want to allow tenant B to pull manifests from the GCS bucket\n> belonging to tenant B, but only from this bucket.\n\nFor example, I would like to have the following configuration:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1\nkind: Bucket\nmetadata:\n  name: tenant-a-bucket\n  namespace: tenant-a\nspec:\n  ...\n  provider: gcp\n  serviceAccountName: tenant-a-gcs-sa\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-a-gcs-sa\n  namespace: tenant-a\n  annotations:\n    iam.gke.io/gcp-service-account: tenant-a-bucket@my-org-project.iam.gserviceaccount.com\n---\napiVersion: source.toolkit.fluxcd.io/v1\nkind: Bucket\nmetadata:\n  name: tenant-b-bucket\n  namespace: tenant-b\nspec:\n  ...\n  provider: gcp\n  serviceAccountName: tenant-b-gcs-sa\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-b-gcs-sa\n  namespace: tenant-b\n  annotations:\n    iam.gke.io/gcp-service-account: tenant-b-bucket@my-org-project.iam.gserviceaccount.com\n```\n\n#### Story 4\n\n> As a cluster administrator, I want to allow tenant A to decrypt secrets using\n> the AWS KMS key belonging to tenant A, but only this key. At the same time,\n> I want to allow tenant B to decrypt secrets using the AWS KMS key belonging\n> to tenant B, but only this key.\n\nFor example, I would like to have the following configuration:\n\n```yaml\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: tenant-a-aws-kms\n  namespace: tenant-a\nspec:\n  ...\n  decryption:\n    provider: sops\n    serviceAccountName: tenant-a-aws-kms-sa\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-a-aws-kms-sa\n  namespace: tenant-a\n  annotations:\n    eks.amazonaws.com/role-arn: arn:aws:iam::123456789123:role/tenant-a-kms\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: tenant-b-aws-kms\n  namespace: tenant-b\nspec:\n  ...\n  decryption:\n    provider: sops\n    serviceAccountName: tenant-b-aws-kms-sa\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-b-aws-kms-sa\n  namespace: tenant-b\n  annotations:\n    eks.amazonaws.com/role-arn: arn:aws:iam::123456789123:role/tenant-b-kms\n```\n\n#### Story 5\n\n> As a cluster administrator, I want to allow tenant A to publish notifications\n> to the `tenant-a` topic in Google Cloud Pub/Sub, but only to this topic. At\n> the same time, I want to allow tenant B to publish notifications to the\n> `tenant-b` topic in Google Cloud Pub/Sub, but only to this topic. I want\n> to do so without creating any GCP IAM Service Accounts.\n\nFor example, I would like to have the following configuration:\n\n```yaml\napiVersion: notification.toolkit.fluxcd.io/v1beta3\nkind: Provider\nmetadata:\n  name: tenant-a-google-pubsub\n  namespace: tenant-a\nspec:\n  ...\n  type: googlepubsub\n  serviceAccountName: tenant-a-google-pubsub-sa\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-a-google-pubsub-sa\n  namespace: tenant-a\n---\napiVersion: notification.toolkit.fluxcd.io/v1beta3\nkind: Provider\nmetadata:\n  name: tenant-b-google-pubsub\n  namespace: tenant-b\nspec:\n  ...\n  type: googlepubsub\n  serviceAccountName: tenant-b-google-pubsub-sa\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-b-google-pubsub-sa\n  namespace: tenant-b\n```\n\n#### Story 6\n\n> As a cluster administrator, I want to allow tenant A to use a GCP\n> Service Account to apply resources in a remote GKE cluster with\n> Kubernetes RBAC permissions granted to this GCP Service Account,\n> and tenant B to do the same using a different GCP Service Account.\n\nFor example, I would like to have the following configuration:\n\n```yaml\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: tenant-a-gke\n  namespace: tenant-a\nspec:\n  ...\n  kubeConfig:\n    provider: gcp\n    serviceAccountName: tenant-a-gke-sa\n    cluster: projects/<project-id>/locations/<location>/clusters/<cluster-name>\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-a-gke-sa\n  namespace: tenant-a\n---\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: tenant-b-gke\n  namespace: tenant-b\nspec:\n  ...\n  kubeConfig:\n    provider: gcp\n    serviceAccountName: tenant-b-gke-sa\n    cluster: projects/<project-id>/locations/<location>/clusters/<cluster-name>\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tenant-b-gke-sa\n  namespace: tenant-b\n```\n\n### Alternatives\n\n#### An alternative for identifying Flux resources in cloud providers\n\nInstead of issuing `ServiceAccount` tokens in the Kubernetes API we could\ncome up with a username naming scheme for Flux resources and issue tokens\nfor these usernames instead, e.g. `flux:<resource type>:<namespace>:<name>`.\nThis would make each Flux object have its own identity instead of using\n`ServiceAccounts` for this purpose. This choice would then prevent cases\nof other Flux objects from malicious actors in the same namespace from\nabusing the permissions granted to the `ServiceAccount` of the object.\nThis choice, however, would provide a worse user experience, as Flux and\nKubernetes users are already used to the `ServiceAccount` resource being\nthe identity for resources in the cluster, not only in the context of plain\nRBAC but also in the context of workload identity.\nThis choice would also require the introduction of new APIs for configuring\nthe respective cloud identities in the Flux objects, when such APIs already\nexist as defined by the cloud providers themselves as annotations in the\n`ServiceAccount` resources. We therefore choose to stick with the well-known\npattern of using `ServiceAccounts` for configuring the identities of the\nFlux resources. Furthermore, as mentioned in the\n[Multi-Tenancy Model](#multi-tenancy-model) section, the tenant trust domains\nare namespaces, so a tenant is expected to control and have access to all\nthe resources `ServiceAccounts` in their namespaces are allowed to access.\n\n#### Alternatives for modifying controller RBAC to create `ServiceAccount` tokens\n\nIn this section we discuss alternatives for changing the RBAC of controllers for\ncreating `ServiceAccount` tokens cluster-wide, as it has a potential impact on\nthe security posture of Flux.\n\n1. We grant RBAC permissions to the `ServiceAccounts` of the Flux controllers\n  (that would implement multi-tenant workload identity) for creating tokens\n  for any other `ServiceAccounts` in the cluster.\n2. We require users to grant \"self-impersonation\" to the `ServiceAccounts` so they\n  can create tokens for themselves. The controller would then impersonate the\n  `ServiceAccount` when creating a token for it. This operation would then only\n  succeed if the `ServiceAccount` has been correctly granted permission to create\n  a token for itself.\n\nIn both alternatives the controller `ServiceAccount` would require some form\nof cluster-wide impersonation permission. Alternative 2 requires impersonation\npermission to be granted directly to the controller `ServiceAccount`, while\nin alternative 1, impersonation permission would be indirectly granted by the\nprocess of creating a token for another `ServiceAccount`. By creating a token\nfor another `ServiceAccount`, the controller `ServiceAccount` effectively has\nthe same permissions as the `ServiceAccount` it is creating the token for, as\nit could simply use the token to impersonate the `ServiceAccount`. Therefore\nit is reasonable to affirm that both alternatives are equivalent in terms of\nsecurity.\n\nTo break the tie between the two alternatives we introduce the fact that\nalternative 1 eliminates operational burden on users. In fact, native\nworkload identity for pods does not require users to grant this\nself-impersonation permission to the `ServiceAccounts` of the pods.\n\nWe therefore choose alternative 1.\n\n## Design Details\n\nFor detailing the proposal we need to first introduce the technical\nbackground on how workload identity is implemented by the managed\nKubernetes services from the cloud providers.\n\n### Technical Background\n\nWorkload identity in Kubernetes is based on\n[OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html)\n(OIDC).\nThe *Kubernetes `ServiceAccount` token issuer*, included as the `iss` JWT claim in the\nissued tokens, and represented by the default URL `https://kubernetes.default.svc.cluster.local`,\nimplements the OIDC discovery protocol. Essentially, this means that the Kubernetes API\nwill respond requests to the URL\n`https://kubernetes.default.svc.cluster.local/.well-known/openid-configuration`\nwith a JSON document similar to the one below:\n\n```json\n{\n  \"issuer\": \"https://kubernetes.default.svc.cluster.local\",\n  \"jwks_uri\": \"https://172.18.0.2:6443/openid/v1/jwks\",\n  \"response_types_supported\": [\n    \"id_token\"\n  ],\n  \"subject_types_supported\": [\n    \"public\"\n  ],\n  \"id_token_signing_alg_values_supported\": [\n    \"RS256\"\n  ]\n}\n```\n\nAnd to the URL `https://172.18.0.2:6443/openid/v1/jwks`, *discovered* through the field\n`.jwks_uri` in the JSON response above, the Kubernetes API will respond a JSON document\nsimilar to the following:\n\n```json\n{\n  \"keys\": [\n    {\n      \"use\": \"sig\",\n      \"kty\": \"RSA\",\n      \"kid\": \"NWm3YKmazJPVP7tttzkmSxUn0w8LGGp7yS2CanEF-A8\",\n      \"alg\": \"RS256\",\n      \"n\": \"lV2tbw9hnz1mseah2kMQNe5sRju4mPLlK0F7np97lLNC49G8yc5TMjyciLF3qsDNFCfWyYmsuGlcRg2BIBBX_jkpIUUjlsktdHhuqO2RnOqyRtNuljlT_b0QJgpgxCqq0DHI31EBc0JALOVd6EjjlhsVvVzZOw_b9KBXVS3D3RENuT0_FWauDq5NYbyYnjlvk-vUXCRMNDQSDNwx6X6bktwsmeDRXtM_bP3DokmnMYc4n0asTEg14L6VKky0ByF88Wi1-y0Pm0BHdobDGt1cIeUDeThk4E79JCHxkT5urAyYHcNwcfU4q-tnD6bTpNkFVsk3cqqK2nF7R_7ac5arSQ\",\n      \"e\": \"AQAB\"\n    }\n  ]\n}\n```\n\nThis JSON document contains the public keys for verifying the signature of the issued tokens.\n\nBy querying these two URLs in sequence, cloud providers are able to fetch the information\nrequired for verifying and trusting the tokens issued by the Kubernetes API. Most specifically,\nfor trusting the `sub` JWT claim, which contains the Kubernetes `ServiceAccount` reference\n(name and namespace) for which the token was issued for, i.e. the `ServiceAccount` properly\nsaid.\n\nBy allowing permissions to be granted to `ServiceAccounts` in the cloud provider,\nthe cloud provider is then able to allow Kubernetes `ServiceAccounts` to access its resources.\nThis is usually done by a *Security Token Service* (STS) that exchanges the Kubernetes token\nfor a short-lived cloud provider access token, which is then used to access the cloud provider\nresources.\n\nIt's important to mention that the Kubernetes `ServiceAccount` token issuer URL must be\ntrusted by the cloud provider, i.e. users must configure this URL as a trusted identity\nprovider.\n\nThis process forms the basis for workload identity in Kubernetes. As long as the issuer\nURL can be reached by the cloud provider, this process can take place successfully.\n\nThe reachability of the issuer URL by the cloud provider is where the implementation\nof workload identity starts to differ between cloud providers. For example, in GCP\none can configure the content of the JWKS document directly in the GCP IAM console,\nwhich eliminates the need for network calls to the Kubernetes API. In AWS, on the\nother hand, this is not possible, the process has to be followed strictly, i.e. the\nissuer URL must be reachable by the AWS STS service.\n\nFurthermore, GKE automatically\ncreates the necessary trust relationship between the Kubernetes issuer and the GCP\nSTS service (i.e. automatically injects the JWKS document of the GKE cluster in the\nSTS database), while in EKS this must be done manually by users (an OIDC provider\nmust be created for each EKS cluster).\n\nAnother difference is that the issuer URL remains the default/private one in GKE,\nwhile in EKS it is automatically set to a public one. This is done through\nthe `--service-account-issuer` flag in the `kube-apiserver` command line arguments\n([docs](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-issuer-discovery)). This is a nice feature, as it allows external\nsystems to federate access for workloads running in EKS clusters, e.g. EKS workloads\ncan have federated access to GCP resources.\n\nYet another difference between cloud providers that sheds light in our proposal is\nhow applications running inside pods from the managed Kubernetes services obtain\nthe short-lived cloud provider access tokens. In GCP, the GCP libraries used by\nthe applications attempt to retrieve tokens from the *metadata server*, which is\nreachable by all pods running in GKE. This server creates a token for the\n`ServiceAccount` of the calling pod in the Kubernetes API, exchanges it for a\nshort-lived GCP access token, and returns it to the application. In AKS, on the\nother hand, pods are mutated to include a\n[*token volume projection*](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#serviceaccount-token-volume-projection). The kubelet mounts and automatically\nrotates a volume with a token file inside the pod. The Azure libraries used by\nthe applications then read this file periodically to perform the token exchange\nwith the Azure STS service.\n\nAnother aspect of workload identity that is important for this RFC is how the cloud\nidentities are associated with the Kubernetes `ServiceAccounts`. In most cases, an\nidentity from the IAM service of the cloud provider (e.g. a GCP IAM Service Account,\nor an AWS IAM Role) is associated with a Kubernetes `ServiceAccount` by the process\nof *impersonation*. Permission to impersonate the cloud identity is granted to the\n`ServiceAccount` through a configuration that points to the fully qualified name of\nthe Kubernetes `ServiceAccount`, i.e. the name and namespace of the `ServiceAccount`\nand which cluster it belongs to in the name/address system of the cloud provider.\n\nBecause the cloud provider needs to support this impersonation permission, some\ncloud providers go further and even remove the impersonation requirement, by\nallowing permissions to be granted directly to `ServiceAccounts` (if it needs to\nsupport granting the impersonation permission, then it can probably also easily\nsupport granting any other permissions depending on the implementation). GCP for\nexample has implemented this feature [recently](https://cloud.google.com/blog/products/identity-security/make-iam-for-gke-easier-to-use-with-workload-identity-federation), a GCP IAM\nService Account is no longer required for workload identity, i.e. GCP IAM\npermissions can now be granted directly to Kubernetes `ServiceAccounts`. This is\na significant improvement in the user experience, as it significantly reduces\nthe required configuration steps. AWS implemented a similar feature called *EKS\nPod Identity*, but it still requires an IAM Role to be associated with the\n`ServiceAccount`. The minor improvement from the user experience perspective is\nthat this association is implemented entirely in the AWS EKS/IAM APIs, no\nannotations are required in the Kubernetes `ServiceAccount`. Another improvement\nfrom this EKS feature compared to *IAM Roles for Service Accounts* is that users\nno longer need to create an *OIDC Provider* for the EKS cluster in the IAM API.\n\nIt should be noted, however, that in the feature proposed here we cannot support\n*EKS Pod Identity* because it requires the `ServiceAccount` token to be bound to\na pod, and the Kubernetes API will only issue a `ServiceAccount` token bound to a\npod if that pod uses the respective `ServiceAccount`, which is a requirement we\nsimply cannot meet. The only pod guaranteed to exist from the perspective of a\nFlux controller is itself. Therefore, it's impossible to support EKS Pod Identity\nfor multi-tenant workload identity. (It is already possible to use it for\nsingle-tenant workload identity, though.)\n\nIn sight of the technical background presented above, our proposal becomes simpler.\nThe only solution to support multi-tenant workload identity at the object-level for\nthe Flux APIs is to associate the Flux objects with Kubernetes `ServiceAccounts`.\nWe propose building the `ServiceAccount` token creation and exchange logic into\nthe Flux controllers through a library in the `github.com/fluxcd/pkg` repository.\n\n### API Changes and Feature Gates\n\nFor all the Flux APIs interacting with cloud providers (except `Kustomization`,\nsee the paragraph below), we propose introducing the field `spec.serviceAccountName`\n(if not already present) for specifying the Kubernetes `ServiceAccount` on the same\nnamespace of the object that must be used for getting access to the respective cloud\nresources. This field would be optional, and when not present the original behavior\nwould be observed, i.e. the feature only activates when the field is present and a\ncloud provider among `aws`, `azure` or `gcp` is specified in the `spec.provider`\nfield. So if only the `spec.provider` field is present and set to a cloud provider,\nthen the controller would use single-tenant workload identity as it would prior to\nthe implementation of this RFC, i.e. it would use its own identity for the operation.\n\nNote that this RFC does not seek to change the behavior when `spec.provider` is set\nto `generic` (or left empty, when it defaults to `generic`), in which case the field\n`spec.secretRef` can be used for specifying the Kubernetes `Secret` containing the\ncredentials (or `spec.serviceAccountName` in the case of the APIs dealing with\ncontainer registries, through the `imagePullSecrets` field of the `ServiceAccount`).\n\nThe `Kustomization` API uses Key Management Services (KMS) for decrypting\nSOPS-encrypted secrets. We propose adding the dedicated optional field\n`spec.decryption.serviceAccountName` for multi-tenant workload identity\nwhen intercting with the KMS service. We choose having a dedicated field\nfor the `Kustomization` API because the field `spec.serviceAccountName`\nalready exists and is used for a major part of the functionality which\nis authenticating with the Kubernetes API when applying resources. If\nwe used the same field for both purposes users would be forced to use\nmulti-tenancy for both cloud and Kubernetes API interactions. Furthermore,\nthe cloud provider in the `Kustomization` API is detected by the SOPS SDK\nitself while decrypting the secrets, so we don't need to introduce\n`spec.decryption.provider` for this purpose.\n\nThe `Kustomization` and `HelmRelease` APIs have the field\n`spec.kubeConfig.secretRef` for specifying a Kubernetes `Secret` containing\na static kubeconfig for accessing a remote Kubernetes cluster. We propose\nadding `spec.kubeConfig.configMapRef` for specifying a Kubernetes `ConfigMap`\nthat is mutually exclusive with `spec.kubeConfig.secretRef` for supporting\nworkload identity for both managed Kubernetes services from the cloud\nproviders and also a `generic` provider. The fields in the `ConfigMap`\nwould be the following:\n- `data.provider`: The provider to use for obtaining the temporary\n  `*rest.Config` for the remote cluster. One of `generic`, `aws`, `azure`\n  or `gcp`. Required.\n- `data.cluster`: Used only by `aws`, `azure` and `gcp`. The fully qualified\n  name of the cluster resource in the respective cloud provider API. Needed\n  for obtaining the unspecified fields `data.address` and `data[\"ca.crt\"]`\n  (not required if both are specified).\n- `data.address`: The HTTPS address of the API server of the remote cluster.\n  Required for `generic`, optional for `aws`, `azure` and `gcp`.\n- `data.serviceAccountName`: The optional Kubernetes `ServiceAccount` to use\n  for obtaining access to the remote cluster, implementing object-level\n  workload identity. If not specified, the controller identity will be used.\n- `data.audiences`: The audiences Kubernetes `ServiceAccount` tokens must\n  be issued for as a list of strings in YAML format. Optional. Defaults to\n  `data.address` for `generic`, and has hardcoded default/specific values for\n  `aws`, `azure` and `gcp` depending on the provider.\n- `data[\"ca.crt\"]`: The optional PEM-encoded CA certificate of the remote\n  cluster.\n\nFor remote cluster access, the configured identity, be it controller-level\nor object-level, must have the necessary permissions to:\n- Access the cluster resource in the cloud provider API to get the\n  cluster CA certificate and the cluster API server address. This is\n  only necessary if one of `data.address` or `data[\"ca.crt\"]` is not\n  specified in the `ConfigMap`. In other words, at least two of the\n  three fields `data.address`, `data[\"ca.crt\"]` and `data.cluster`\n  must be specified. If both `data.address` and `data[\"ca.crt\"]`\n  are specified, then the `data.cluster` field *must not* be specified,\n  the controller will error out if it is. If only `data.cluster` and\n  `data.address` are specified, then `data.address` has to match at\n  least one of the addresses of the cluster resource in the cloud\n  provider API. If only `data.cluster` and `data[\"ca.crt\"]` are\n  specified, then the first address of the cluster resource in the\n  cloud provider API will be used as the address of the remote cluster\n  and the CA returned by the cloud provider API will be ignored.\n  If only `data.cluster` is specified, then the first address\n  of the cluster resource in the cloud provider API will be used.\n- The relevant permissions for applying and managing the target resources\n  in the remote cluster. For cloud providers this means either Kubernetes\n  RBAC or the cloud provider API permissions, as managed Kubernetes services\n  support authorizing requests through both ways.\n- When used with `spec.serviceAccountName`, the authenticated identity must\n  have the necessary permissions to impersonate this `ServiceAccount` in the\n  remote cluster (related [bug](https://github.com/fluxcd/pkg/issues/959)).\n\nTo enable using the new `serviceAccountName` fields, we propose introducing\na feature gate called `ObjectLevelWorkloadIdentity` in the controllers that\nwould support the feature. In the first release we should make it opt-in so\ncluster admins can consciously roll it out. If the feature gate is disabled\nand users set the field a terminal error should be returned.\n\n### Workload Identity Library\n\nWe propose using the Go package `github.com/fluxcd/pkg/auth`\nfor implementing a workload identity library that can be\nused by all the Flux controllers that need to interact\nwith cloud providers. This library would be responsible\nfor creating the `ServiceAccount` tokens in the Kubernetes\nAPI and exchanging them for short-lived access tokens\nfor the cloud provider. The library would also be responsible\nfor caching the tokens when configured by users.\n\nThe library should support both single-tenant and multi-tenant workload\nidentity because single-tenant implementations are already supported in\nGA APIs and hence they must remain available for backwards compatibility.\nFurthermore, it would be easier to support both use cases in a single\nlibrary as opposed to mingling a new library into the currently existing\nones, so this new library becomes the definitive unified solution for\nworkload identity in Flux.\n\nThe library should automatically detect whether the workload identity\nis single-tenant or multi-tenant by checking if a `ServiceAccount` was\nconfigured for the operation. If a `ServiceAccount` was configured, then\nthe operation is multi-tenant, otherwise it is single-tenant and the\ngranted access token must represent the identity associated with the\ncontroller.\n\nThe directory structure would look like this:\n\n```shell\n.\n└── auth\n    ├── aws\n    │   └── aws.go\n    ├── azure\n    │   └── azure.go\n    ├── gcp\n    │   └── gcp.go\n    ├── access_token.go\n    ├── options.go\n    ├── provider.go\n    ├── registry.go\n    ├── restconfig.go\n    └── token.go\n```\n\nThe file `auth/token.go` would contain the token abstraction:\n\n```go\npackage auth\n\n// Token is an interface that represents an access token that can be used to\n// authenticate with a cloud provider. The only common method is for getting the\n// duration of the token, because different providers have different ways of\n// representing the token. For example, Azure and GCP use a single string,\n// while AWS uses three strings: access key ID, secret access key and token.\n// Consumers of this interface should know what type to cast it to.\ntype Token interface {\n\t// GetDuration returns the duration for which the token is valid relative to\n\t// approximately time.Now(). This is used to determine when the token should\n\t// be refreshed.\n\tGetDuration() time.Duration\n}\n```\n\nThe file `auth/access_token.go` would contain the main algorithm for getting access tokens:\n\n```go\npackage auth\n\n// GetAccessToken returns an access token for accessing resources in the given cloud provider.\nfunc GetAccessToken(ctx context.Context, provider Provider, opts ...Option) (Token, error) {\n\t// 1. Check if a ServiceAccount is configured and return the controller access token if not (single-tenant WI).\n\t// 2. Get the provider audience for creating the OIDC token for the ServiceAccount in the Kubernetes API.\n\t// 3. Get the ServiceAccount using the configured controller-runtime client.\n\t// 4. Get the provider identity from the ServiceAccount annotations and add it to the options.\n\t// 5. Build the cache key using the configured options.\n\t// 6. Get the token from the cache. If present, return it, otherwise continue.\n\t// 7. Create an OIDC token for the ServiceAccount in the Kubernetes API using the provider audience.\n\t// 8. Exchange the OIDC token for an access token through the Security Token Service of the provider.\n\t// 9. Add the final token to the cache and return it.\n}\n```\n\nThe file `auth/registry.go` would contain the logic for creating artifact registry credentials:\n\n```go\npackage auth\n\n// ArtifactRegistryCredentials is a particular type implementing the Token interface\n// for credentials that can be used to authenticate against an artifact registry\n// from a cloud provider.\ntype ArtifactRegistryCredentials struct {\n\tauthn.Authenticator\n\tExpiresAt time.Time\n}\n\nfunc (r *ArtifactRegistryCredentials) GetDuration() time.Duration {\n\treturn time.Until(r.ExpiresAt)\n}\n\n// GetArtifactRegistryCredentials retrieves the registry credentials for the\n// specified artifact repository and provider.\nfunc GetArtifactRegistryCredentials(ctx context.Context, provider Provider,\n\tartifactRepository string, opts ...Option) (*ArtifactRegistryCredentials, error)\n```\n\nThe file `auth/restconfig.go` would contain the logic for creating a REST config for the Kubernetes API:\n\n```go\npackage auth\n\n// RESTConfig is a particular type implementing the Token interface\n// for Kubernetes REST configurations.\ntype RESTConfig struct {\n\tHost        string\n\tBearerToken string\n\tCAData      []byte\n\tExpiresAt   time.Time\n}\n\n// GetDuration implements Token.\nfunc (r *RESTConfig) GetDuration() time.Duration {\n\treturn time.Until(r.ExpiresAt)\n}\n\n// GetRESTConfig retrieves the authentication and connection\n// details to a remote Kubernetes cluster for the given provider,\n// cluster resource name and API server address.\nfunc GetRESTConfig(ctx context.Context, provider Provider,\ncluster, address string, opts ...Option) (*RESTConfig, error)\n```\n\nThe file `auth/provider.go` would contain the `Provider` interface:\n\n```go\npackage auth\n\n// Provider contains the logic to retrieve security credentials\n// for accessing resources in a cloud provider.\ntype Provider interface {\n\t// GetName returns the name of the provider.\n\tGetName() string\n\n\t// NewControllerToken returns a token that can be used to authenticate\n\t// with the cloud provider retrieved from the default source, i.e. from\n\t// the environment of the controller pod, e.g. files mounted in the pod,\n\t// environment variables, local metadata services, etc.\n\tNewControllerToken(ctx context.Context, opts ...Option) (Token, error)\n\n\t// GetAudience returns the audience the OIDC tokens issued representing\n\t// ServiceAccounts should have. This is usually a string that represents\n\t// the cloud provider's STS service, or some entity in the provider for\n\t// which the OIDC tokens are targeted to.\n\tGetAudience(ctx context.Context, serviceAccount corev1.ServiceAccount) (string, error)\n\n\t// GetIdentity takes a ServiceAccount and returns the identity which the\n\t// ServiceAccount wants to impersonate, by looking at annotations.\n\tGetIdentity(serviceAccount corev1.ServiceAccount) (string, error)\n\n\t// NewToken takes a ServiceAccount and its OIDC token and returns a token\n\t// that can be used to authenticate with the cloud provider. The OIDC token is\n\t// the JWT token that was issued for the ServiceAccount by the Kubernetes API.\n\t// The implementation should exchange this token for a cloud provider access\n\t// token through the provider's STS service.\n\tNewTokenForServiceAccount(ctx context.Context, oidcToken string,\n\t\tserviceAccount corev1.ServiceAccount, opts ...Option) (Token, error)\n\n\t// GetAccessTokenOptionsForArtifactRepository returns the options that must be\n\t// passed to the provider to retrieve access tokens for an artifact repository.\n\tGetAccessTokenOptionsForArtifactRepository(artifactRepository string) ([]Option, error)\n\n\t// ParseArtifactRepository parses the artifact repository to verify\n\t// it's a valid repository for the provider. As a result, it returns\n\t// the input required for the provider to issue registry credentials.\n\t// This input is included in the cache key for the issued credentials.\n\tParseArtifactRepository(artifactRepository string) (string, error)\n\n\t// NewArtifactRegistryCredentials takes the registry input extracted by\n\t// ParseArtifactRepository() and an access token and returns credentials\n\t// that can be used to authenticate with the registry.\n\tNewArtifactRegistryCredentials(ctx context.Context, registryInput string,\n\t\taccessToken Token, opts ...Option) (*ArtifactRegistryCredentials, error)\n\n\t// GetAccessTokenOptionsForCluster returns the options that must be\n\t// passed to the provider to retrieve access tokens for a cluster.\n\t// More than one access token may be required depending on the\n\t// provider, with different options (e.g. scope). Hence the return\n\t// type is a slice of slices.\n\tGetAccessTokenOptionsForCluster(cluster string) ([][]Option, error)\n\n\t// NewRESTConfig returns a RESTConfig that can be used to authenticate\n\t// with the Kubernetes API server. The access tokens are used for looking\n\t// up connection details like the API server address and CA certificate\n\t// data, and for accessing the cluster API server itself via the IAM\n\t// system of the cloud provider. If it's just a single token or multiple,\n\t// it depends on the provider.\n\tNewRESTConfig(ctx context.Context, accessTokens []Token, opts ...Option) (*RESTConfig, error)\n}\n```\n\nThe file `auth/options.go` would contain the following options:\n\n```go\npackage auth\n\n// Options contains options for configuring the behavior of the provider methods.\n// Not all providers/methods support all options.\ntype Options struct {\n\tClient          client.Client\n\tCache           *cache.TokenCache\n\tServiceAccount  *client.ObjectKey\n\tInvolvedObject  cache.InvolvedObject\n\tAudiences       []string\n\tScopes          []string\n\tSTSRegion       string\n\tSTSEndpoint     string\n\tProxyURL        *url.URL\n\tClusterResource string\n\tClusterAddress  string\n\tCAData          string\n\tAllowShellOut   bool\n}\n\n// WithServiceAccount sets the ServiceAccount reference for the token\n// and a controller-runtime client to fetch the ServiceAccount and\n// create an OIDC token for it in the Kubernetes API.\nfunc WithServiceAccount(saRef client.ObjectKey, client client.Client) Option {\n\t// ...\n}\n\n// WithCache sets the token cache and the involved object for recording events.\nfunc WithCache(cache cache.TokenCache, involvedObject cache.InvolvedObject) Option {\n\t// ...\n}\n\n// WithAudiences sets the audiences for the Kubernetes ServiceAccount token.\nfunc WithAudiences(audiences ...string) Option {\n\t// ...\n}\n\n// WithScopes sets the scopes for the token.\nfunc WithScopes(scopes ...string) Option {\n\t// ...\n}\n\n// WithSTSRegion sets the region for the STS service (some cloud providers\n// require a region, e.g. AWS).\nfunc WithSTSRegion(stsRegion string) Option {\n\t// ...\n}\n\n// WithSTSEndpoint sets the endpoint for the STS service.\nfunc WithSTSEndpoint(stsEndpoint string) Option {\n\t// ...\n}\n\n// WithProxyURL sets a *url.URL for an HTTP/S proxy for acquiring the token.\nfunc WithProxyURL(proxyURL url.URL) Option {\n\t// ...\n}\n\n// WithCAData sets the CA data for credentials that require a CA,\n// e.g. for Kubernetes REST config.\nfunc WithCAData(caData string) Option {\n\t// ...\n}\n\n// WithClusterResource sets the cluster resource for creating a REST config.\n// Must be the fully qualified name of the cluster resource in the cloud\n// provider API.\nfunc WithClusterResource(clusterResource string) Option {\n\t// ...\n}\n\n// WithClusterAddress sets the cluster address for creating a REST config.\n// This address is used to select the correct cluster endpoint and CA data\n// when the provider has a list of endpoints to choose from, or to simply\n// validate the address against the cluster resource when the provider\n// returns a single endpoint. This is optional, providers returning a list\n// of endpoints will select the first one if no address is provided.\nfunc WithClusterAddress(clusterAddress string) Option {\n\t// ...\n}\n\n// WithAllowShellOut allows the provider to shell out to binary tools\n// for acquiring controller tokens. MUST be used only by the Flux CLI,\n// i.e. in the github.com/fluxcd/flux2 Git repository.\nfunc WithAllowShellOut() Option {\n\t// ...\n}\n```\n\nThe `auth/aws/aws.go`, `auth/azure/azure.go` and\n`auth/gcp/gcp.go` files would contain the implementations for\nthe respective cloud providers:\n\n```go\npackage aws\n\nimport (\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/credentials\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sts/types\"\n)\n\nconst ProviderName = \"aws\"\n\ntype Provider struct{}\n\ntype Token struct{ types.Credentials }\n\n// GetDuration implements auth.Token.\nfunc (t *Token) GetDuration() time.Duration {\n\treturn time.Until(*t.Expiration)\n}\n\ntype credentialsProvider struct {\n\topts []auth.Option\n}\n\n// NewCredentialsProvider creates an aws.CredentialsProvider for the aws provider.\nfunc NewCredentialsProvider(opts ...auth.Option) aws.CredentialsProvider {\n\treturn &credentialsProvider{opts}\n}\n\n// Retrieve implements aws.CredentialsProvider.\nfunc (c *credentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {\n\t// Use auth.GetToken() to get the token.\n}\n```\n\n```go\npackage azure\n\nimport (\n\t\"github.com/Azure/azure-sdk-for-go/sdk/azcore\"\n\t\"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy\"\n)\n\nconst ProviderName = \"azure\"\n\ntype Provider struct{}\n\ntype Token struct{ azcore.AccessToken }\n\n// GetDuration implements auth.Token.\nfunc (t *Token) GetDuration() time.Duration {\n\treturn time.Until(t.ExpiresOn)\n}\n\ntype tokenCredential struct {\n\topts []auth.Option\n}\n\n// NewTokenCredential creates an azcore.TokenCredential for the azure provider.\nfunc NewTokenCredential(opts ...auth.Option) azcore.TokenCredential {\n\treturn &tokenCredential{opts}\n}\n\n// GetToken implements azcore.TokenCredential.\n// The options argument is ignored, any options should be\n// specified in the constructor.\nfunc (t *tokenCredential) GetToken(ctx context.Context, _ policy.TokenRequestOptions) (azcore.AccessToken, error) {\n\t// Use auth.GetToken() to get the token.\n}\n```\n\n```go\npackage gcp\n\nimport (\n\t\"golang.org/x/oauth2\"\n)\n\nconst ProviderName = \"gcp\"\n\ntype Provider struct {}\n\ntype Token struct{ oauth2.Token }\n\n// GetDuration implements auth.Token.\nfunc (t *Token) GetDuration() time.Duration {\n\treturn time.Until(t.Expiry)\n}\n\ntype tokenSource struct {\n\tctx context.Context\n\topts []auth.Option\n}\n\n// NewTokenSource creates an oauth2.TokenSource for the gcp provider.\nfunc NewTokenSource(ctx context.Context, opts ...auth.Option) oauth2.TokenSource {\n\treturn &tokenSource{ctx, opts}\n}\n\n// Token implements oauth2.TokenSource.\nfunc (t *tokenSource) Token() (*oauth2.Token, error) {\n\t// Use auth.GetToken() to get the token.\n}\n\nvar gkeMetadata struct {\n\tprojectID      string\n\tlocation       string\n\tname           string\n\tmu             sync.Mutex\n\tloaded         bool\n}\n```\n\nAs detailed above, each cloud provider implementation defines a simple wrapper\naround the cloud provider access token type. This wrapper implements the\n`auth.Token` interface, which is essentially the method `GetDuration()`\nfor the cache library to manage the token lifetime. The wrappers also contain\na helper function to create a token source for the respective cloud provider\nSDKs. These methods have different names and signatures because the cloud provider\nSDKs are different and have different types, but they all implement the same\nconcept of a token source.\n\nThe `aws` provider needs to read the environment variable `AWS_REGION` for\nconfiguring the STS client. Even though a specific STS endpoint may be\nconfigured, the AWS SDKs require the region to be set regardless. This\nvariable is usually set automatically in EKS pods, and can be manually set\nby users otherwise (e.g. in Fargate pods).\n\nAn important detail to take into account in the `azure` provider implementation\nis using our custom implementation of `azidentity.NewDefaultAzureCredential()`\nfound in kustomize-controller for SOPS decryption. This custom implementation\navoids shelling out to the Azure CLI, which is something we strive to avoid in\nthe Flux codebase. This is important because today we are doing this in a few\nAPIs but not others, so it will be a significant improvement to implement this\nin a single place and use it everywhere.\n\nThe `gcp` provider needs to load the cluster metadata from the `gke-metadata-server`\nin order to create tokens. This must be done lazily when the first token is\nrequested, and there's a very important reason for this: if this was done on\nthe controller startup, the controller would crash when running outside GKE and\nenter `CrashLoopBackOff` because the `gke-metadata-server` would never be\navailable. This is a very important detail that must be taken into account when\nimplementing the `gcp` provider. The cluster metadata doesn't change during the\nlifetime of the controller pod, so we use a `sync.Mutex` and `bool` to load it\nonly once into a package variable.\n\nWhen not running in GKE, the `gcp` provider would use the following annotation\nin the `ServiceAccount` to identify the Workload Identity Provider resource\nfor use with Workload Identity Federation:\n\n```yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: my-service-account\n  namespace: my-namespace\n  annotations:\n    gcp.auth.fluxcd.io/workload-identity-provider: projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID\n```\n\n#### Cache Key\n\nThe cache key *MUST* include *ALL* the inputs specified for acquiring the\ntemporary credentials, as they all obviously influence how the credentials\nare created.\n\n##### Format\n\nThe cache key would be the SHA256 hash of the following multi-line strings:\n\nSingle-tenant/controller-level access token cache key:\n\n```\nprovider=<cloud-provider-name>\nscopes=<comma-separated-scopes>\nstsRegion=<sts-region>\nstsEndpoint=<sts-endpoint>\nproxyURL=<proxy-url>\ncaData=<ca-data>\n```\n\nMulti-tenant/object-level access token cache key:\n\n```\nprovider=<cloud-provider-name>\nproviderIdentity=<cloud-provider-identity>\nserviceAccountName=<service-account-name>\nserviceAccountNamespace=<service-account-namespace>\nserviceAccountTokenAudiences=<comma-separated-audiences>\nscopes=<comma-separated-scopes>\nstsRegion=<sts-region>\nstsEndpoint=<sts-endpoint>\nproxyURL=<proxy-url>\ncaData=<ca-data>\n```\n\nArtifact registry credentials:\n\n```\naccessTokenCacheKey=sha256(<access-token-cache-key>)\nartifactRepositoryCacheKey=<'gcp'-for-gcp|registry-region-for-aws|registry-host-for-azure>\n```\n\nREST config:\n\n```\naccessToken1CacheKey=sha256(<cache-key-for-access-token-1>)\n...\naccessTokenNCacheKey=sha256(<cache-key-for-access-token-N>)\ncluster=<cluster-resource-name>\naddress=<cluster-api-server-address>\n```\n\n##### Security Considerations and Controls\n\nAs mentioned previously, a `ServiceAccount` must have permission to impersonate the\nidentity it is configured to impersonate. Once a token for the impersonated identity\nis issued, that token would be valid for a while even if immediately after issuing it\nthe `ServiceAccount` loses permission to impersonate that identity. In our cache key\ndesign, the token would remain available for the `ServiceAccount` to use until it\nexpires. If the impersonation permission was revoked to mitigate an attack, the\nattacker could still get a valid token from the cache for a while after the\nrevocation, and hence still exercise the permissions they had prior to the revocation.\n\nThere are a few mitigations for this scenario:\n\n* Users that revoke impersonation permissions for a `ServiceAccount` must also\n  change the annotations of the `ServiceAccount` to impersonate a different identity,\n  or delete the `ServiceAccount` altogether, or restart the Flux controllers so the\n  cache is purged. Any of these actions would effectively prevent the attack, but\n  they represent an additional step after revoking the impersonation permission.\n\n* In the Flux controllers users can specify the `--token-cache-max-duration` flag,\n  which can be used to limit the maximum duration for which a token can be cached.\n  By reducing the default maximum duration of one hour to a smaller value, users can\n  limit the time window during which a token would be available for a `ServiceAccount`\n  to use after losing permission to impersonate the identity.\n\n* Disable cache entirely by setting the flag `--token-cache-max-size=0`, or removing\n  this flag altogether since the default is already zero i.e. no tokens are cached\n  in the Flux controller. This mitigation is in case your security requirements are\n  extreme and you want to avoid any risk of such an attack. This mitigation is the\n  most effective, but it comes with the cost of many API calls to issue tokens in\n  the cloud provider, which could result in a performance bottleneck and/or\n  throttling/rate-limiting, as tokens would have to be issued for every\n  reconciliation.\n\nA similar situation could occur in the single-tenant scenario, when the permission\nto impersonate the configured identity is revoked from the controller `ServiceAccount`.\nIn this case, the attacker would have access to the cloud provider resources that\nthe controller had access to prior to the revocation of the impersonation permission.\nMost of the mitigations mentioned above apply to this scenario as well, except for\nthe one that involves changing the annotations of the `ServiceAccount` to impersonate\na different identity or deleting the `ServiceAccount` altogether, as the controller\n`ServiceAccount` should not be deleted. The best mitigation in this case is to restart\nthe Flux controllers so the cache is purged.\n\n### Library Integration\n\nWhen reconciling an object, the controller must use the `auth.GetToken()`\nfunction passing a `controller-runtime` client that has permission to create\n`ServiceAccount` tokens in the Kubernetes API, the desired cloud provider by name,\nand all the remaining options according to the configuration of the controller and\nof the object. The provider names match the ones used for `spec.provider` in the Flux\nAPIs, i.e. `aws`, `azure` and `gcp`.\n\nBecause different cloud providers have different ways of representing their access\ntokens (e.g. Azure and GCP tokens are a single opaque string while AWS has three\nstrings: access key ID, secret access key and token), consumers of the\n`auth.Token` interface would need to cast it to `*<provider>.Token`.\n\nThe following subsections show details of how the integration would look like.\n\n#### `GitRepository` and `ImageUpdateAutomation` APIs\n\nFor these APIs the only provider we have so far that supports workload identity\nis `azure`. In this case we would simply replace `AzureOpts []azure.OptFunc` in\nthe `fluxcd/pkg/git.ProviderOptions` struct with `[]fluxcd/pkg/auth.Option`\nand would modify `fluxcd/pkg/git.GetCredentials()` to use `auth.GetToken()`.\nThe token interface would be cast to `*azure.Token` and the token string would be\nassigned to `fluxcd/pkg/git.Credentials.BearerToken`. A `GitRepository` object\nconfigured with the `azure` provider and a `ServiceAccount` would then go through\nthis code path.\n\n#### `OCIRepository`, `ImageRepository`, `ImagePolicy`, `HelmRepository` and `HelmChart` APIs\n\nThe `HelmRepository` API only supports a cloud provider for OCI repositories, so\nfor all these APIs we would only need to support OCI authentication.\n\nAll these APIs currently use `*fluxcd/pkg/oci/auth/login.Manager` to get the\ncontainer registry credentials. The new library would replace this library\nentirely, as it mostly handles single-tenant workload identity. The new library\ncovers both single-tenant and multi-tenant workload identity, so it would be\na drop-in replacement for the `login.Manager`.\n\nIn the case of the source-controller APIs, all of them use the function `OIDCAuth()`\nfrom the internal package `internal/oci`. We would replace the use of `login.Manager`\nwith `auth.GetToken()` in this function. The token interface would\nbe cast to `*auth.RegistryCredentials` and then fed to `authn.FromConfig()`\nfrom the package `github.com/google/go-containerregistry/pkg/authn`.\n\nIn the case of `ImageRepository` and `ImagePolicy`, we would replace `login.Manager` with\n`auth.GetToken()` in the `setAuthOptions()` method of the\n`ImageRepositoryReconciler`, cast the token to `*auth.RegistryCredentials`\nand then feed it to `authn.FromConfig()`.\n\nThe beauty of this particular integration is that here we no longer require\nbranching code paths for each cloud provider, we would just need to configure\nthe options for the `auth.GetToken()` function and the library would take\ncare of the rest.\n\n#### `Bucket` API\n\n##### Provider `aws`\n\nA `Bucket` object configured with the `aws` provider and a `ServiceAccount` would\ncause the internal `minio.MinioClient` of source-controller to be created with the\nfollowing new options:\n\n* `minio.WithTokenClient(controller-runtime/pkg/client.Client)`\n* `minio.WithTokenCache(*fluxcd/pkg/cache.TokenCache)`\n\nThe constructor would then use `auth.GetToken()` to get the\ncloud provider access token. When doing so, the `minio.MinioClient` would\ncast the token interface to `*aws.Token` and feed it to `credentials.NewStatic()`\nfrom the package `github.com/minio/minio-go/v7/pkg/credentials`.\n\n##### Provider `azure`\n\nA `Bucket` object configured with the `azure` provider and a `ServiceAccount`\nwould cause the internal `azure.BlobClient` of source-controller to be created\nwith the following new options:\n\n* `azure.WithTokenClient(controller-runtime/pkg/client.Client)`\n* `azure.WithTokenCache(*fluxcd/pkg/cache.TokenCache)`\n* `azure.WithServiceAccount(controller-runtime/pkg/client.ObjectKey)`\n* `azure.WithInvolvedObject(*fluxcd/pkg/cache.InvolvedObject)`\n\nThe constructor would then use `azure.NewTokenCredential()` to feed this\ntoken credential to `azblob.NewClient()`.\n\n##### Provider `gcp`\n\nA `Bucket` object configured with the `gcp` provider and a `ServiceAccount`\nwould cause the internal `gcp.GCSClient` of source-controller to be created\nwith the following new options:\n\n* `gcp.WithTokenClient(controller-runtime/pkg/client.Client)`\n* `gcp.WithTokenCache(*fluxcd/pkg/cache.TokenCache)`\n* `gcp.WithServiceAccount(controller-runtime/pkg/client.ObjectKey)`\n* `gcp.WithInvolvedObject(*fluxcd/pkg/cache.InvolvedObject)`\n\nThe constructor would then use `gcp.NewTokenSource()` to feed this token\nsource to the `option.WithTokenSource()` and pass it to\n`cloud.google.com/go/storage.NewClient()`.\n\n#### `Kustomization` API (SOPS Decryption)\n\nThe `Kustomization` API uses Key Management Services (KMS) for decrypting\nSOPS secrets. The internal packages `internal/decryptor` and `internal/sops`\nof kustomize-controller already use interfaces compatible with the new\nlibrary in the case of `aws` and `azure`, i.e. `*awskms.CredentialsProvider`\nand `*azkv.TokenCredential` respectively, so we could easily use the helper\nfunctions for creating the respective token sources to configure the KMS\ncredentials for SOPS. This is thanks to the respective SOPS libraries\n`github.com/getsops/sops/v3/kms` and `github.com/getsops/sops/v3/azkv`.\nFor GCP we can introduce the equivalent interface that was recently added\nin [this](https://github.com/getsops/sops/pull/1794/files) pull request.\nThis new interface introduced in SOPS upstream can also be used for the\ncurrent JSON credentials method that we use via\n`google.CredentialsFromJSON().TokenSource`. This would allow us to use only\nthe respective token source interfaces for all three providers when using\neither workload identity or secrets.\n\n#### `Kustomization` and `HelmRelease` APIs (Remote Cluster Access)\n\nThe kustomize-controller should fetch a `*rest.Config` from the `auth`\npackage and feed it to `runtime/client.WithKubeConfig()` for creating\na `runtime/client.(*Impersonator)` with the configured authentication.\n\nThe helm-controller should fetch a `*rest.Config` from the `auth`\npackage and feed it to the internal `kube.NewMemoryRESTClientGetter()`,\njust like it does for the secret-based alternative.\n\n#### `Provider` API\n\nThe constructor of the internal `notifier.Factory` of notification-controller\nwould now accept the following new options:\n\n* `notifier.WithTokenClient(controller-runtime/pkg/client.Client)`\n* `notifier.WithTokenCache(*fluxcd/pkg/cache.TokenCache)`\n* `notifier.WithServiceAccount(controller-runtime/pkg/client.ObjectKey)`\n* `notifier.WithInvolvedObject(*fluxcd/pkg/cache.InvolvedObject)`\n\nThe cloud provider types that support workload identity would then use these\noptions. See the following subsections for details.\n\n##### Type `azuredevops`\n\nThe `notifier.NewAzureDevOps()` constructor would use the existing and new\noptions to call `auth.GetToken()` and use it to get the cloud\nprovider access token. When doing so, the `notifier.AzureDevOps` would cast\nthe token interface to `*azure.Token` and feed the token string to\n`NewPatConnection()` from the package\n`github.com/microsoft/azure-devops-go-api/azuredevops/v6`.\n\n##### Type `azureeventhub`\n\nThe `notifier.NewAzureEventHub()` constructor would use the existing and new\noptions to call `auth.GetToken()` and use it to get the cloud\nprovider access token. When doing so, the `notifier.AzureEventHub` would cast\nthe token interface to `*azure.Token` and feed the token string to `newJWTHub()`.\n\n##### Type `googlepubsub`\n\nThe `notifier.NewGooglePubSub()` constructor would use the existing and new\noptions to call `gcp.NewTokenSource()` and feed this token source to the\n`option.WithTokenSource()` and pass it to `cloud.google.com/go/pubsub.NewClient()`.\n\n## Implementation History\n\n* In Flux 2.6 object-level workload identity was introduced for the\n  OCI artifact APIs, i.e. `OCIRepository`, `ImageRepository`, `ImagePolicy`,\n  `HelmRepository` and `HelmChart`, as well as for SOPS decryption\n  in the `Kustomization` API and Azure Event Hubs in the\n  `Provider` API.\n* In Flux 2.7 object-level workload identity was introduced for all\n  the remaining APIs that support cloud providers, i.e. `Bucket`,\n  `GitRepository` and `ImageUpdateAutomation`, and also all the\n  remaining types for the `Provider` API, i.e. `azuredevops` and\n  `googlepubsub`. In addition, support for controller and\n  object-level workload identity was introduced for the\n  `Kustomization` and `HelmRelease` APIs for remote cluster\n  access.\n"
  },
  {
    "path": "rfcs/0011-opentelemetry-tracing/README.md",
    "content": "# RFC-0011: OpenTelemetry Tracing\n\n**Status:** implemented\n\n**Creation date:** 2025-04-24\n\n**Last update:** 2026-03-13\n\n## Summary\nThe aim is to be able to collect traces via OpenTelemetry (OTel) across all Flux related objects, such as HelmReleases, Kustomizations and among others. These may be sent towards a tracing provider where may be potentially stored and visualized. Flux does not have any responsibility on storing and visualizing those, it keeps being completely stateless. Thereby, being seamless for the user, the implementation is going to be part of the already existing `Alert` API Type. Therefore, `EventSources` is going to discriminate the events belonging to the specific sources, which are going to be looked up to and send them out towards the `Provider` set. In this way, it could facilitate the observability and monitoring of Flux related objects.\n\n## Motivation\nThis RFC was born out of a need for end-to-end visibility into Flux’s multi-controller GitOps workflow. At the time Flux was one monolithic controller; it has since split into several specialized controllers (source-, kustomize-, helm-, notification-, etc.), which makes tracing the path of a single \"Source change → applied resource → notification” much harder. Additionally, users may not have to implement tools/sidecars around to maintain.\n\nCorrelate any potential source (GitRepository, OCIRepository, HelmChart or Bucket) with all downstream actions. Therefore, you would like to see a single trace (with multiple spans underneath):\n- Alert reference based on a unique ID (root trace).\n- Any source pulling new content based on a new Digest Checksum.\n- Any subsequent reconciliation that ran.\n- Events emitted and notifications sent by the notification-controller.\n\nOn top of this, can be built custom UIs that surface trace timelines alongside Git commit or Docker image tags, so operators can say “what exactly happened when I tagged v1.2.3?” in a single pane of glass. \n\n### Goals\n- **End-to-end GitOps traceability:** Capture the traces that follows \"a Git change\" (any source) through all Flux controllers for simply debugging and root-cause analysis.\n- **Declarative, CRD-driven configuration:** Reuse the concept of `Alerts` to be able to populate this feature over, out-of-the-box. Therefore, users can link `EventSources` and `Provider` where trace will be sent.\n- **Notification Controller as the trace-collector:** Leverage the notification-controller's existing event watching pipeline to ingest reconciliation events and turn them into OpenTelemetry spans, being forwarded to an OpenTelemetry-compatible backend - `Provider`.\n- **Cross-controller span correlation:** Ensure spans are emitted from multiple, stateless controller can be stitched together into a single trace by using Flux \"revision\" annotation.\n\n### Non-Goals\n- **Not a full-tracing backend:** We won't build or bundle a storage/visualization system. Users may have to still rely on a external collector for long-term retention, querying and UI.\n- **Not automatic instrumentation of user workloads:** This integration only captures Flux controller events (Source, Kustomize, Helm, etc.). It won't auto-inject spans into your application pods or third-party controllers running in the same cluster.\n- **Not a replacement for metrics or logs:** Flux's existing Prometheus metrics and structural logging remain the primary way to monitor performance and errors. Tracing is purely for request-flow visibility, not for time-series monitoring or log aggregation.\n- **No deep-code level spans beyond CRUD events:** Will emit spans around high-level reconciliation steps (e.g. \"reconcile GitRepository\", \"dispatch Notification\"), but we're not aiming to instrument every internal function call or library method within each controller.\n- **Not a service mesh integration:** It's not plan of the scope tying this into Istio, Linkerd, or other mesh-sidecar approaches. It's strictly a controller-drive, CRD-based model.\n- **No per-span custom enrichment beyond basic metadata:** At least initially, it won't support complex span attributes or tag-enrichment rules. You may have to handle those in your downstream collector/processor if needed.\n- **Not a replacement for user-driven OpenTelemetry SDKs:** If you already have a Go-based operator that embed OpenTelemetry's SDK directly, this feature won't override or duplicate that. Think about it as a complementary, declarative layer for flux controllers.\n\n## Proposal\n<!--\nThis is where we get down to the specifics of what the proposal actually is.\nThis should have enough detail that reviewers can understand exactly what\nyou're proposing, but should not include things like API designs or\nimplementation.\n\nIf the RFC goal is to document best practices,\nthen this section can be replaced with the actual documentation.\n-->\nThe implementation will extend the notification-controller with OpenTelemetry tracing capabilities by leveraging the existing Alert API object model and adding a new Provider API type called `otel`. This approach maintains Flux's declarative configuration paradigm while adding powerful distributed tracing functionality.\n\n### Core Implementation Strategy\n1. **Extend the notification-controller:** Add OpenTelemetry tracing support to the notification-controller via adding a new type, `otel`. Which already has visibility into events across the Flux ecosystem.\n2. **Leverage existing Alert CRD structure:** Use the Alert Kind API object as the configuration entry point, where:\n     - `EventSources` define which Flux resources to trace (GitRepositories, Kustomizations, HelmReleases, etc.).\n     - `Provider` specifies where to send the trace data (any OpenTelemetry-compatible backends).\n3. **Span generation and correlation:** Generate spans for each reconciliation event from watched resources, ensuring proper parent-child relationships and context propagation using Flux's revision annotations as correlation identifiers.\n4. **Provider compatibility and fallback mechanism:** The implementation supports any provider that implements the OpenTelemetry Protocol (OTLP). When traces are sent to OTLP-compatible providers (like Jaeger or Tempo), they are transmitted as proper OpenTelemetry spans via HTTP(s) requests (no gRPC support at this moment). For non-OTLP providers, the system gracefully degrades by logging trace information as structured warnings in the notification-controller logs, ensuring no alerting functionality is disrupted. This approach maintains system stability while encouraging the use of proper tracing backends.\n\n\nThis approach allows users to declaratively configure tracing using familiar Flux patterns, without requiring code changes to their applications or additional sidecar deployments. The notification-controller will handle the collection, correlation, and forwarding of spans to the configured tracing backend.\n\nExample Configuration:\n```yaml\n# Configure the alert\napiVersion: notification.toolkit.fluxcd.io/v1beta3\nkind: Alert\nmetadata:\n  name: webapp-tracing\n  namespace: default\nspec:\n  providerRef: \n    name: otel-collector\n  eventSources:\n    - kind: GitRepository  # Source controller resources\n      name: webapp-source\n    - kind: Kustomization  # Kustomize controller resources\n      name: webapp-backend\n    - kind: Kustomization  # Kustomize controller resources\n      name: webapp-frontend\n  eventMetadata:\n    env: staging\n    cluster: cluster-1\n    region: us-east-2\n---\n# Define a tracing provider\napiVersion: notification.toolkit.fluxcd.io/v1beta3\nkind: Provider\nmetadata:\n  name: otel-collector\n  namespace: default\nspec:\n  type: otel\n    address: http://otel-collector.observability.svc.cluster.local:4318/v1/traces # OTEL Collector endpoint\n  secretRef:\n    name: otel-collector-secret  # Optional: auth + additional headers\n  certSecretRef:\n    name: mtls-certs # Optional: enable mTLS auth\n  proxySecretRef:\n    name: otel-collector-proxy # Optional: proxy configuration\n---\n# OTEL Collector secret\napiVersion: v1\nkind: Secret\nmetadata:\n  name: otel-collector-secret\n  namespace: default\nstringData:\n  # Headers data prevails over auth fields (username/password or token)\n  # Must be used either username/password or token (considers if username is set in order to discriminate bearer token auth or basic auth)\n  username: \"<otel-collector-username>\"\n  password: \"<otel-collector-password>\"\n  token: \"<otel-collector-api-token>\"\n  headers: |\n    X-Forwarded-Proto: https\n---\n# TLS Certificates and keys\napiVersion: v1\nkind: Secret\nmetadata:\n  name: mtls-certs\n  namespace: default\ntype: kubernetes.io/tls # or Opaque\nstringData:\n  # All fields are required to enable mTLS\n  tls.crt: |\n    -----BEGIN CERTIFICATE-----\n    <client certificate>\n    -----END CERTIFICATE-----\n  tls.key: |\n    -----BEGIN PRIVATE KEY-----\n    <client private key>\n    -----END PRIVATE KEY-----\n  # Just ca.crt in case of CA-only\n  ca.crt: | \n    -----BEGIN CERTIFICATE-----\n    <certificate authority certificate>\n    -----END CERTIFICATE-----\n---\n# Proxy configuration\napiVersion: v1\nkind: Secret\nmetadata:\n  name: otel-collector-proxy\n  namespace: default\nstringData:\n  address: \"http://<otel-collector-proxy-url>(:<otel-collector-proxy-port>)\"\n  username: \"<otel-collector-proxy-username>\"\n  password: \"<otel-collector-proxy-password>\"\n```\n\nBased on this configuration, the notification-controller will:\n- Watch for events from the specified resources.\n- Generate OpenTelemetry spans for each reconciliation event.\n- Correlate spans across controllers using Flux's revision annotations.\n- Forward the spans to the configured OTEL Collector endpoint - `Provider`.\n- This implementation maintains Flux's stateless design principles while providing powerful distributed tracing capabilities that help users understand and troubleshoot their GitOps workflows.\n\n### Alternatives\n<!--\nList plausible alternatives to the proposal and explain why the proposal is superior.\n\nThis is a good place to incorporate suggestions made during discussion of the RFC.\n-->\n\n## Design Details\n\n### Trace Identity and Correlation\nA key challenge in distributed tracing is establishing a reliable correlation mechanism that works across multiple controllers in a stateless, potentially unreliable environment. Our solution addresses this with a robust span identification strategy.\n\nThe Trace ID is generated using a deterministic approach that combines:\n- **Alert Object UID** (guaranteed unique by Kubernetes across all clusters).\n- **Source's revision ID** (extracted from event payloads).\n\nThese values are concatenated and passed through a configurable checksum algorithm (SHA-256 by default). This approach ensures:\n- Globally unique trace identifiers across multi-tenant and multi-cluster environments.\n- Consistent trace correlation even when events arrive out of order.\n- Reliable identification of the originating source event.\n\nExample:\n```yaml\n# Input values\nAlert UID: \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"\nSource Revision: \"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae\"\n\n# Concatenated value\n\"a1b2c3d4-e5f6-7890-abcd-ef1234567890(<Alert-UID>):sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae(<source-revision>)\"\n\n# Apply SHA-256 (default algorithm)\nTrace ID: \"f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650\"\n```\nWhen events occur in the system:\n1. GitRepository reconciliation event with revision \"sha256:2c26...\" is captured by notification controller and creates a new trace with ID \"f7846f55...\" and therefore, a new Span underneath.\n2. Kustomization acts on the previous one, creating another span, reusing the same trace ID and then linked to \"f7846f55...\". Being both under the same trace.\n3. All spans are collected into a single trace viewable in the tracing backend.\n\n### Resilient Span Management\nThe design accounts for the distributed nature of Flux controllers and potential delays/downtimes that a distributed system always implies:\n- **Asynchronous Event Processing:** Since events may arrive in any order due to the distributed nature of Flux controllers, the system doesn't assume sequential processing. Each event can independently locate its parent span or create a new root span as needed.\n- **Fault Tolerance:** If the notification-controller experiences downtime or latency issues, it implements a recovery mechanism:\n  - When processing an event, it first attempts to locate an existing root span based on the calculated ID.\n  - If found, it attaches the new span as a child to maintain the trace hierarchy.\n  - If not found (due to previous failures or out-of-order processing), it automatically creates a new root span\n- Span Hierarchy Maintenance: All subsequent spans related to the same revision are properly attached to their parent spans, creating a coherent trace visualization regardless of when events are processed.\n\nThis design ensures trace continuity even in challenging distributed environments while maintaining Flux's core principles of statelessness and resilience.\n\n## Implementation History\n\n* RFC implemented and generally available in Flux [v2.7.0](https://github.com/fluxcd/flux2/releases/tag/v2.7.0)\n"
  },
  {
    "path": "rfcs/0012-external-artifact/README.md",
    "content": "# RFC-0012 External Artifact\n\n**Status:** implemented\n\n**Creation date:** 2025-04-08\n\n**Last update:** 2026-03-13\n\n## Summary\n\nThis RFC proposes the introduction of a new API called `ExternalArtifact` that would allow\n3rd party controllers to act as a source of truth for the cluster desired state. In effect,\nthe `ExternalArtifact` API acts as an extension of the existing `source.toolkit.fluxcd.io` APIs\nthat enables Flux `kustomize-controller` and `helm-controller` to consume artifacts from external\nsource types that are not natively supported by `source-controller`.\n\n## Motivation\n\nOver the years, we've received requests from users to support other source types besides the\nones natively supported by `source-controller`. For example, users have asked for support of\ndownloading Kubernetes manifests from GitHub/GitLab releases, Omaha protocol, SFTP protocol,\nand other remote storage systems.\n\nAnother common request is to run transformations on the artifacts fetched by source-controller.\nFor example, users want to be able to generate YAML manifests from jsonnet, cue, and other\ntemplating engines before they are consumed by Flux `kustomize-controller`.\n\nIn order to support these use cases, we need to define a standard API that allows 3rd party\ncontrollers to expose artifacts in-cluster (in the same way `source-controller` does)\nthat can be consumed by Flux  `kustomize-controller` and `helm-controller`.\n\n### Goals\n\nDefine a standard API for 3rd party controllers to expose artifacts that can be consumed by\nFlux controllers in the same way as the existing `source.toolkit.fluxcd.io` APIs.\n\nAllow Flux users to transition from using `source-controller` to using 3rd party source controllers\nwith minimal changes to their existing `Kustomizations` and `HelmReleases`.\n\n### Non-Goals\n\nAllow arbitrary custom resources to be referenced in Flux `Kustomization` and `HelmRelease` as `sourceRef`.\n\nExtend the Flux controllers permissions to access custom resources that are not part of the\n`source.toolkit.fluxcd.io` APIs.\n\n## Proposal\n\nAssuming we have a custom controller called `release-controller` that is responsible for\nreconciling `GitHubRelease` custom resources. This controller downloads the Kubernetes\ndeployment YAML manifests from the GitHub API and stores them in a local file system\nas a `tar.gz` file. The `release-controller` then creates an `ExternalArtifact`\ncustom resource that tells the Flux controllers from where to fetch the artifact.\n\nEvery time the `release-controller` reconciles a `GitHubRelease` custom resource,\nit updates the `ExternalArtifact` status with the latest artifact information if the\nupstream release has changed.\n\nThe `release-controller` is responsible for exposing a HTTP endpoint that serves\nthe artifacts from its own storage. The URL of the `tar.gz` artifact is stored in\nthe `ExternalArtifact` status and should be accessible from the Flux controllers\nrunning in the cluster.\n\nExample of a generated `ExternalArtifact` custom resource:\n\n```yaml\napiVersion: source.toolkit.fluxcd.io/v1\nkind: ExternalArtifact\nmetadata:\n  name: podinfo\n  namespace: apps\nspec:\n  # SourceRef points to the Kubernetes custom resource for\n  # which the artifact is generated.\n  # +optional\n  sourceRef:\n    apiVersion: source.example.com/v1alpha1\n    kind: GitHubRelease\n    name: podinfo\n    namespace: apps\nstatus:\n  artifact:\n    # Digest is the digest of the tar.gz file in the form of '<algorithm>:<checksum>'.\n    # The digest is used by the Flux controllers to verify the integrity of the artifact.\n    # +required\n    digest: sha256:35d47c9db0eee6ffe08a404dfb416bee31b2b79eabc3f2eb26749163ce487f52\n    # LastUpdateTime is the timestamp corresponding to the last update of the\n    # Artifact in storage.\n    # +required\n    lastUpdateTime: \"2025-03-21T13:37:31Z\"\n    # Path is the relative file path of the Artifact. It can be used to locate\n    # the file in the root of the Artifact storage on the local file system of\n    # the controller managing the Source.\n    # +required\n    path: release/apps/podinfo/6.8.0-b3396ad.tar.gz\n    # Revision is a human-readable identifier traceable in the origin source system\n    # in the form of '<human-readable-identifier>@<algorithm>:<checksum>'.\n    # The revision is used by the Flux controllers to determine if the artifact has changed.\n    # +required\n    revision: 6.8.0@sha256:35d47c9db0eee6ffe08a404dfb416bee31b2b79eabc3f2eb26749163ce487f52\n    # Size is the number of bytes of the tar.gz file.\n    # +required\n    size: 20914\n    # URL is the in-cluster HTTP address of the Artifact as exposed by the controller\n    # managing the Source. It can be used to retrieve the Artifact for\n    # consumption, e.g. by kustomize-controller applying the Artifact contents.\n    # +required\n    url: http://release-controller.flux-system.svc.cluster.local./release/apps/podinfo/6.8.0-b3396ad.tar.gz\n  conditions:\n  - lastTransitionTime: \"2025-04-08T09:09:49Z\"\n    message: stored artifact for release 6.8.0\n    observedGeneration: 1\n    reason: Succeeded\n    status: \"True\"\n    type: Ready\n```\n\nNote that the `.status.artifact` is identical to how `source-controller` exposes the\nartifact information for `Bucket`, `GitRepository`, and `OCIRepository` custom resources.\nThis allows the Flux controllers to consume external artifacts with minimal changes.\n\nThe `ExternalArtifact` custom resource is referenced by a Flux `Kustomization` as follows:\n\n```yaml\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: podinfo\n  namespace: apps\nspec:\n  interval: 10m\n  sourceRef:\n    kind: ExternalArtifact\n    name: podinfo\n  path: \"./\"\n  prune: true\n```\n\nFlux `kustomize-controller` will then fetch the artifact from the URL specified in the\n`ExternalArtifact` status, verifies the integrity of the artifact using the digest\nand applies the contents of the artifact to the cluster.\n\nLike with the existing `source.toolkit.fluxcd.io` APIs, `kustomize-controller` will\nwatch the `ExternalArtifact` custom resource for changes and will re-apply the\ncontents of the artifact when the `.status.artifact.revision` changes.\n\nWhen the `ExternalArtifact` contains a Helm chart, it can be referenced by a Flux `HelmRelease` as follows:\n\n```yaml\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: podinfo\n  namespace: apps\nspec:\n  interval: 10m\n  releaseName: podinfo\n  chartRef:\n    kind: ExternalArtifact\n    name: podinfo\n  values:\n    replicaCount: 2\n```\n\n### Security Considerations\n\nWith the introduction of the `ExternalArtifact` API, the trust boundary of Flux is extended\nto include 3rd party controllers that are capable of creating and managing `ExternalArtifact`\ncustom resources in the cluster. This means that the security posture of the cluster\nis now dependent on the security of these 3rd party controllers.\n\nTo mitigate potential security risks, it is recommended to implement the following measures \nwhen developing 3rd party source controllers:\n\n- **Authentication and Authorization**: Ensure that the controller uses proper authentication\n  and authorization mechanisms to interact with upstream sources and avoid embedding sensitive\n  information directly in the custom resource specifications. Following source-controller\n  best practices for managing credentials is highly recommended: use `serviceAccountName` to\n  integrate with Kubernetes Workload Identity for short-lived credentials, use `secretRef` to\n  reference long-lived credentials, never cache long-lived credentials on disk or in-memory.\n- **TLS Encryption**: Use TLS encryption for all communications between the controller\n  and upstream sources to protect sensitive data in transit. Following source-controller\n  best practices for TLS is highly recommended: use `certSecretRef` to reference\n  custom CA certificates and client certificates, prefer Mutual TLS authentication, never\n  allow skipping TLS verification.\n- **Provenance and Integrity**: Ensure that the controller verifies the integrity of the\n  artifacts it generates and exposes in-cluster. This can be achieved by using checksums\n  and digital signatures to validate the authenticity of upstream sources. Following\n  source-controller best practices for source integrity is highly recommended:\n  verify the provenance of upstream artifacts using Sigstore Cosign or Notary\n  Notation signatures, prefer keyless verification using OIDC identity tokens and\n  public transparency logs.\n- **Access Control**: Implement access control mechanisms to restrict cross-namespace\n  generation of `ExternalArtifact` custom resources. Following source-controller\n  best practices for access control is highly recommended: expose a `--no-cross-namespace-refs`\n  flag to restrict the controller from generating `ExternalArtifact` resources in a different\n  namespace than the one where the source custom resource is located. Use Kubernetes owner\n  references to establish a clear ownership relationship between the source custom resource\n  and the `ExternalArtifact` resource, allowing Kubernetes garbage collection to clean up\n  the `ExternalArtifact` when the source resource is deleted.\n- **Least Privilege**: Run the controller with the least privilege necessary to perform\n  its functions. Following source-controller best practices for least privilege is highly recommended:\n  use a dedicated Kubernetes service account with minimal RBAC permissions, avoid running\n  the controller as a cluster-admin or with wildcard permissions, conform with the restricted pod security\n  standard (e.g., disallow running as root, disallow host network access, read-only rootfs).\n- **Artifact persistent storage integrity**: Ensure that the controller can be configured to use\n  persistent storage for storing artifacts, to avoid data loss in case of controller restarts\n  or failures. Following source-controller best practices for artifact storage is highly recommended:\n  at startup, ensure that the artifacts in-storage have not been tampered with by verifying\n  the checksums of all stored artifacts against the `ExternalArtifact` digests in the cluster.\n- **Artifact access restrictions**: If the controller is deployed outside of flux-system namespace,\n  it should include network policies that restrict access to the artifact storage endpoint to only\n  kustomize-controller and helm-controller.\n  Following source-controller best practices for network policies is highly recommended:\n  use Kubernetes NetworkPolicies to restrict ingress and egress traffic to/from the controller pods,\n  allowing only necessary communication with upstream sources and trusted consumers.\n\n### User Stories\n\n#### 3rd Party Source Controller\n\nAs a 3rd party controller developer, I want to expose artifacts in-cluster that are sourced from `flatcar/nebraska`\nthat can be consumed by Flux `kustomize-controller` and `helm-controller` so that Flux users can use my controller\nas a source of truth for their cluster desired state.\n\n#### Custom Source Transform\n\nAs a Flux user, I want to use a custom controller that generates Kubernetes manifests from CUE templates \nwhich can be consumed by Flux `kustomize-controller`.\n\n#### Policy Enforcement\n\nAs a cluster administrator, I want to ensure that only trusted 3rd party controllers\ncan create and manage `ExternalArtifact` resources in the cluster.\n\n```yaml\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingAdmissionPolicy\nmetadata:\n  name: \"trusted-external-artifacts\"\nspec:\n  failurePolicy: Fail\n  matchConstraints:\n    resourceRules:\n    - apiGroups:   [\"source.toolkit.fluxcd.io\"]\n      apiVersions: [\"v1\"]\n      operations:  [\"CREATE\", \"UPDATE\"]\n      resources:   [\"externalartifacts\"]\n  validations:\n    # Restrict the sourceRef to only allow trusted APIs\n    - expression: >\n        object.spec.sourceRef.apiVersion.startsWith('source.example.com')\n    # Restrict the sourceRef to only allow trusted Kinds\n    - expression: >\n        object.spec.sourceRef.kind == 'GitHubRelease' || \n        object.spec.sourceRef.kind == 'GitLabRelease'\n    # Restrict the artifacts to be served only by trusted endpoints within the cluster\n    - expression: >\n        !has(object.status.artifact) ||\n        object.status.artifact.url.startsWith('http://release-controller.flux-system.svc.cluster.local./')\n    # Restrict the artifact operations to trusted service accounts\n    - expression: >\n        request.userInfo.username == 'system:serviceaccount:flux-system:release-controller'\n```\n\n### Alternatives\n\nAn alternative to this proposal would be to deploy an OCI registry in-cluster.\nThe 3rd party controllers would then push the artifacts to the registry\nand Flux `kustomize-controller` and `helm-controller` would consume the artifacts\nvia the `OCIRepository` custom resource.\n\nWhile this approach is feasible, it requires additional infrastructure and\nconfiguration, which may not be desirable for all users. The `ExternalArtifact` API\nprovides a simpler and more flexible way to expose artifacts in-cluster without\nthe need to self-host an OCI registry. In addition, the `ExternalArtifact` API\noffers a better user experience by allowing Flux user to trace the origin of reconciled resources\nback to the original source via `ExternalArtifact.spec.sourceRef` and `flux trace` command.\n\n## Design Details\n\nThe `ExternalArtifact` API will be added to `source-controller/api` Go package.\n3rd party controllers will import `github.com/fluxcd/source-controller/api/v1`\nto generate valid `ExternalArtifact` custom resources using the controller-runtime client.\n\nThe Flux maintainers will develop an SDK for packaging and exposing artifacts\nin-cluster using the `ExternalArtifact` API. The SDK will provide helper functions\nfor generating Flux-compliant artifacts, as well as for storing artifacts in a persistent storage.\nThe SDK will be published as a Go module under the `github.com/fluxcd/pkg` repository.\n\nThe `ExternalArtifact` CRD will be bundled with the `source-controller` CRDs manifests which\nare part of the standard Flux distribution. This means that users will not need to install\nthe `ExternalArtifact` CRD separately, as it will be available out of the box with Flux.\n\nThe `ExternalArtifact` API specifications will be published to the Flux documentation website,\nunder the `source.toolkit.fluxcd.io` API reference section.\n\nThe Flux `Kustomization` and `HelmRelease` APIs will be extended to support the `ExternalArtifact` kind\nas a valid `sourceRef.kind` and `chartRef.kind`. The `kustomize-controller` and `helm-controller`\nwill gain the ability to consume artifacts from `ExternalArtifact` and watch for revision changes.\n\nThe `flux trace` command will be extended to support the `ExternalArtifact` API, allowing Flux users\nto trace any Kubernetes resource in-cluster that originates from an `ExternalArtifact` and see the\n`sourceRef` information that points to the original source.\n\nThe `flux` CLI will implement the `flux get externalartifact` command for listing and status checking\nof `ExternalArtifact` custom resources in the cluster.\n\n### Feature Gate\n\nWhile the `ExternalArtifact` API will be available out of the box with Flux,\nthe ability for `kustomize-controller` and `helm-controller` to consume artifacts\nfrom `ExternalArtifact` resources will be behind a feature gate called `ExternalArtifact`.\n\nThe feature gate will be disabled by default and can be enabled by setting\nthe `--feature-gates=ExternalArtifact=true` flag on the `kustomize-controller`\nand `helm-controller` deployments. This allows cluster administrators to\ncontrol the adoption of the `ExternalArtifact` feature in their clusters.\n\n## Implementation History\n\n* RFC implemented and generally available in Flux [v2.7.0](https://github.com/fluxcd/flux2/releases/tag/v2.7.0)\n"
  },
  {
    "path": "rfcs/README.md",
    "content": "# Flux RFCs\n\nIn many cases, new features and enhancements are proposed on [flux2/discussions](https://github.com/fluxcd/flux2/discussions).\nA proposal is discussed in public by maintainers, contributors, users and other interested parties.\nAfter some form of consensus is reached between participants, the proposed changes go through the\npull request process where the implementation details are reviewed, approved or rejected by maintainers.\n\nSome proposals may be **substantial**, and for these we ask for a design process to be followed\nso that all stakeholders can be confident about the direction Flux is evolving in.\n\nThe \"RFC\" (request for comments) process is intended to provide a consistent and\ncontrolled path for substantial changes to enter Flux.\n\nExamples of substantial changes:\n\n- API additions (new kinds of resources, new relationships between existing APIs)\n- API breaking changes (new required fields, field removals)\n- Security related changes (Flux controllers permissions, tenant isolation and impersonation)\n- Impactful UX changes (new required inputs to the bootstrap process)\n- Drop capabilities (sunset an existing integration with an external service due to security concerns)\n\n## RFC Process\n\n- Before submitting an RFC please discuss the proposal with the Flux community.\n  Start a discussion on GitHub and ask for feedback at the weekly dev meeting.\n  You must find a maintainer willing to sponsor the RFC.\n- Submit an RFC by opening a pull request using [RFC-0000](RFC-0000/README.md) as template.\n- The sponsor will assign the PR to themselves, will label the PR with `area/RFC` and\n  will request other maintainers to begin the review process.\n- Integrate feedback by adding commits without overriding the history.\n- At least two maintainers have to approve the proposal before it can be merged.\n  Approvers must be satisfied that an\n  [appropriate level of consensus](https://github.com/fluxcd/community/blob/main/GOVERNANCE.md#decision-guidelines)\n  has been reached.\n- Before the merge, an RFC number is assigned by the sponsor and the PR branch must be rebased with main.\n- Once merged, the proposal may be implemented in Flux.\n  The progress could be tracked using the RFC number (used as prefix for issues and PRs).\n- After the proposal implementation is available in a release candidate or final release,\n  the RFC should be updated with the Flux version added to the \"Implementation History\" section.\n- During the implementation phase, the RFC could be discarded due to security or performance concerns.\n  In this case, the RFC \"Implementation History\" should state the rejection motives.\n  Ultimately the decision on the feasibility of a particular implementation,\n  resides with the maintainers that reviewed the code changes.\n- A new RFC could be summited with the scope of replacing an RFC rejected during implementation.\n  The new RFC must come with a solution for the rejection motives of the previous RFC.\n"
  },
  {
    "path": "rfcs/RFC-0000/README.md",
    "content": "# RFC-NNNN Title\n\n<!--\nThe title must be short and descriptive.\n-->\n\n**Status:** provisional\n\n<!--\nStatus represents the current state of the RFC.\nMust be one of `provisional`, `implementable`, `implemented`, `deferred`, `rejected`, `withdrawn`, or `replaced`.\n-->\n\n**Creation date:** YYYY-MM-DD\n\n**Last update:** YYYY-MM-DD\n\n## Summary\n\n<!--\nOne paragraph explanation of the proposed feature or enhancement.\n-->\n\n## Motivation\n\n<!--\nThis section is for explicitly listing the motivation, goals, and non-goals of\nthis RFC. Describe why the change is important and the benefits to users.\n-->\n\n### Goals\n\n<!--\nList the specific goals of this RFC. What is it trying to achieve? How will we\nknow that this has succeeded?\n-->\n\n### Non-Goals\n\n<!--\nWhat is out of scope for this RFC? Listing non-goals helps to focus discussion\nand make progress.\n-->\n\n## Proposal\n\n<!--\nThis is where we get down to the specifics of what the proposal actually is.\nThis should have enough detail that reviewers can understand exactly what\nyou're proposing, but should not include things like API designs or\nimplementation.\n\nIf the RFC goal is to document best practices,\nthen this section can be replaced with the actual documentation.\n-->\n\n### User Stories\n\n<!--\nOptional if existing discussions and/or issues are linked in the motivation section.\n-->\n\n### Alternatives\n\n<!--\nList plausible alternatives to the proposal and explain why the proposal is superior.\n\nThis is a good place to incorporate suggestions made during discussion of the RFC.\n-->\n\n## Design Details\n\n<!--\nThis section should contain enough information that the specifics of your\nchange are understandable. This may include API specs and code snippets.\n\nThe design details should address at least the following questions:\n- How can this feature be enabled / disabled?\n- Does enabling the feature change any default behavior?\n- Can the feature be disabled once it has been enabled?\n- How can an operator determine if the feature is in use?\n- Are there any drawbacks when enabling this feature?\n-->\n\n## Implementation History\n\n<!--\nMajor milestones in the lifecycle of the RFC such as:\n- The first Flux release where an initial version of the RFC was available.\n- The version of Flux where the RFC graduated to general availability.\n- The version of Flux where the RFC was retired or superseded.\n-->\n"
  },
  {
    "path": "tests/.gitignore",
    "content": "\n# Created by https://www.toptal.com/developers/gitignore/api/terraform\n# Edit at https://www.toptal.com/developers/gitignore?templates=terraform\n\n### Terraform ###\n# Local .terraform directories\n**/.terraform/*\n*.terraform.lock.hcl\n\n# test files\nbuild/\n\n# .tfstate files\n*.tfstate\n*.tfstate.*\n# Crash log files\ncrash.log\n\n# Exclude all .tfvars files, which are likely to contain sentitive data, such as\n# password, private keys, and other secrets. These should not be part of version\n# control as they are data points which are potentially sensitive and subject\n# to change depending on the environment.\n#\n*.tfvars\n\n# Ignore override files as they are usually used to override resources locally and so\n# are not checked in\noverride.tf\noverride.tf.json\n*_override.tf\n*_override.tf.json\n\n# Include override files you do wish to add to version control using negated pattern\n# !example_override.tf\n\n# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan\n# example: *tfplan*\n\n# Ignore CLI configuration files\n.terraformrc\nterraform.rc\n\n.env\n# End of https://www.toptal.com/developers/gitignore/api/terraform\n"
  },
  {
    "path": "tests/bootstrap/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/fluxcd/go-git-providers/github\"\n\t\"github.com/fluxcd/go-git-providers/gitprovider\"\n\t\"k8s.io/client-go/util/retry\"\n)\n\nfunc main() {\n\tks := \"test-cluster/flux-system/kustomization.yaml\"\n\tpatchName := \"test-cluster/flux-system/gotk-patches.yaml\"\n\tksContent := `apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n- gotk-components.yaml\n- gotk-sync.yaml\npatches:\n  - path: gotk-patches.yaml\n    target:\n      kind: Deployment`\n\tpatchContent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: all-flux-components\nspec:\n  template:\n    metadata:\n      annotations:\n        # Required by Kubernetes node autoscaler\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n    spec:\n      securityContext:\n        runAsUser: 10000\n        fsGroup: 1337\n      containers:\n        - name: manager\n          securityContext:\n            readOnlyRootFilesystem: true\n            allowPrivilegeEscalation: false\n            runAsNonRoot: true\n            capabilities:\n              drop:\n                - ALL\n`\n\tcommitFiles := []gitprovider.CommitFile{\n\t\t{\n\t\t\tPath:    &ks,\n\t\t\tContent: &ksContent,\n\t\t},\n\t\t{\n\t\t\tPath:    &patchName,\n\t\t\tContent: &patchContent,\n\t\t},\n\t}\n\n\torgName := os.Getenv(\"GITHUB_ORG_NAME\")\n\trepoName := os.Getenv(\"GITHUB_REPO_NAME\")\n\tgithubToken := os.Getenv(github.TokenVariable)\n\tclient, err := github.NewClient(gitprovider.WithOAuth2Token(githubToken))\n\tif err != nil {\n\t\tlog.Fatalf(\"error initializing github client: %s\", err)\n\t}\n\n\trepoRef := gitprovider.OrgRepositoryRef{\n\t\tOrganizationRef: gitprovider.OrganizationRef{\n\t\t\tOrganization: orgName,\n\t\t\tDomain:       github.DefaultDomain,\n\t\t},\n\t\tRepositoryName: repoName,\n\t}\n\n\tvar repo gitprovider.OrgRepository\n\terr = retry.OnError(retry.DefaultRetry, func(err error) bool {\n\t\treturn err != nil\n\t}, func() error {\n\t\trepo, err = client.OrgRepositories().Get(context.Background(), repoRef)\n\t\treturn err\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"error getting %s repository in org %s: %s\", repoRef.RepositoryName, repoRef.Organization, err)\n\t}\n\n\t_, err = repo.Commits().Create(context.Background(), \"main\", \"add patch manifest 3\", commitFiles)\n\tif err != nil {\n\t\tlog.Fatalf(\"error making commit: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "tests/image-automation/auto.yaml",
    "content": "apiVersion: image.toolkit.fluxcd.io/v1\nkind: ImageRepository\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  image: ghcr.io/stefanprodan/podinfo\n  interval: 10m\n---\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImagePolicy\nmetadata:\n  name: podinfo\n  namespace: flux-system\nspec:\n  interval: 10m\n  imageRepositoryRef:\n    name: podinfo\n  policy:\n    semver:\n      range: 6.x\n  digestReflectionPolicy: Always\n---\napiVersion: image.toolkit.fluxcd.io/v1\nkind: ImageUpdateAutomation\nmetadata:\n  name: flux-system\n  namespace: flux-system\nspec:\n  interval: 5m0s\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  git:\n    checkout:\n      ref:\n        branch: main\n    commit:\n      author:\n        email: fluxcdbot@users.noreply.github.com\n        name: fluxcdbot\n      messageTemplate: |\n        Automated image update\n\n        Automation name: {{ .AutomationObject }}\n\n        Files:\n        {{ range $filename, $_ := .Changed.FileChanges -}}\n        - {{ $filename }}\n        {{ end -}}\n\n        Changes:\n        {{ range $resource, $changes := .Changed.Objects -}}\n        {{- range $_, $change := $changes }}\n        - {{ $change.OldValue }} -> {{ $change.NewValue }}\n        {{ end -}}\n        {{ end -}}\n    push:\n      branch: main\n  update:\n    path: ./test-cluster/podinfo-auto\n    strategy: Setters\n"
  },
  {
    "path": "tests/image-automation/kustomization.yaml",
    "content": "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nnamespace: flux-system\nresources:\n  - https://raw.githubusercontent.com/stefanprodan/podinfo/6.8.0/kustomize/deployment.yaml\n  - auto.yaml\nimages:\n  - name: ghcr.io/stefanprodan/podinfo\n    newName: ghcr.io/stefanprodan/podinfo # {\"$imagepolicy\": \"flux-system:podinfo:name\"}\n    newTag: 6.8.0 # {\"$imagepolicy\": \"flux-system:podinfo:tag\"}\n    digest: \"sha256:6c1975b871efb327528c84d46d38e6dd7906eecee6402bc270eeb7f1b1a506df\" # {\"$imagepolicy\": \"flux-system:podinfo:digest\"}\n"
  },
  {
    "path": "tests/image-automation/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/fluxcd/go-git-providers/github\"\n\t\"github.com/fluxcd/go-git-providers/gitprovider\"\n\t\"k8s.io/client-go/util/retry\"\n)\n\nfunc main() {\n\tksPath := \"test-cluster/podinfo-auto/kustomization.yaml\"\n\tautoPath := \"test-cluster/podinfo-auto/auto.yaml\"\n\n\tksContent, err := os.ReadFile(\"kustomization.yaml\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tks := string(ksContent)\n\n\tautoContent, err := os.ReadFile(\"auto.yaml\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tauto := string(autoContent)\n\n\tcommitFiles := []gitprovider.CommitFile{\n\t\t{\n\t\t\tPath:    &ksPath,\n\t\t\tContent: &ks,\n\t\t},\n\t\t{\n\t\t\tPath:    &autoPath,\n\t\t\tContent: &auto,\n\t\t},\n\t}\n\n\torgName := os.Getenv(\"GITHUB_ORG_NAME\")\n\trepoName := os.Getenv(\"GITHUB_REPO_NAME\")\n\tgithubToken := os.Getenv(github.TokenVariable)\n\tclient, err := github.NewClient(gitprovider.WithOAuth2Token(githubToken))\n\tif err != nil {\n\t\tlog.Fatalf(\"error initializing github client: %s\", err)\n\t}\n\n\trepoRef := gitprovider.OrgRepositoryRef{\n\t\tOrganizationRef: gitprovider.OrganizationRef{\n\t\t\tOrganization: orgName,\n\t\t\tDomain:       github.DefaultDomain,\n\t\t},\n\t\tRepositoryName: repoName,\n\t}\n\n\tvar repo gitprovider.OrgRepository\n\terr = retry.OnError(retry.DefaultRetry, func(err error) bool {\n\t\treturn err != nil\n\t}, func() error {\n\t\trepo, err = client.OrgRepositories().Get(context.Background(), repoRef)\n\t\treturn err\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"error getting %s repository in org %s: %s\", repoRef.RepositoryName, repoRef.Organization, err)\n\t}\n\n\t_, err = repo.Commits().Create(context.Background(), \"main\", \"automation test\", commitFiles)\n\tif err != nil {\n\t\tlog.Fatalf(\"error making commit: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "tests/integration/Makefile",
    "content": "GO_TEST_ARGS ?=\nPROVIDER_ARG ?=\nTEST_TIMEOUT ?= 60m\nFLUX_BINARY ?= ../../bin/flux\n\ntest: sops-check\n\tmkdir -p build\n\tcp $(FLUX_BINARY) build/flux\n\t# These two versions of podinfo are pushed to the cloud registry and used in tests for ImageUpdateAutomation\n\tdocker pull ghcr.io/stefanprodan/podinfo:6.0.0\n\tdocker pull ghcr.io/stefanprodan/podinfo:6.0.1\n\tgo test -timeout $(TEST_TIMEOUT) -v ./ $(GO_TEST_ARGS) $(PROVIDER_ARG)\n\ntest-azure:\n\t$(MAKE) test PROVIDER_ARG=\"-provider azure\" GO_TEST_ARGS=\"--tags azure $(GO_TEST_ARGS)\"\n\ntest-gcp:\n\t$(MAKE) test PROVIDER_ARG=\"-provider gcp\"\n\ndestroy:\n\tgo test -timeout $(TEST_TIMEOUT) -v ./ $(GO_TEST_ARGS) $(PROVIDER_ARG) -destroy-only\n\ndestroy-azure:\n\t$(MAKE) destroy PROVIDER_ARG=\"-provider azure\"\n\ndestroy-gcp:\n\t$(MAKE) destroy PROVIDER_ARG=\"-provider gcp\"\n\nsops-check:\nifeq ($(shell which sops),)\n\t$(error \"no sops in PATH, consider installing\")\nendif\n"
  },
  {
    "path": "tests/integration/README.md",
    "content": "# E2E Tests\n\nThe goal is to verify that Flux integration with cloud providers are actually working now and in the future.\nCurrently, we only have tests for Azure and GCP.\n\n## General requirements\n\nThese CLI tools need to be installed for each of the tests to run successfully.\n\n- Docker CLI for registry login.\n- [SOPS CLI](https://github.com/mozilla/sops) for encrypting files\n- kubectl for applying certain install manifests.\n\n## Azure\n\n### Architecture\n\nThe [azure](./terraform/azure) Terraform creates the AKS cluster and related resources to run the tests. It creates:\n- An Azure Container Registry\n- An Azure Kubernetes Cluster\n- Two Azure DevOps repositories\n- Azure EventHub for sending notifications\n- An Azure Key Vault\n\n### Requirements\n\n- Azure account with an active subscription to be able to create AKS and ACR,\n  and permission to assign roles. Role assignment is required for allowing AKS workloads to access ACR.\n- Azure CLI, need to be logged in using `az login` as a User or as a Service Principal\n- An Azure DevOps organization, personal access token and ssh keys for accessing repositories\n  within the organization. The scope required for the personal access token is:\n  - `Project and Team` - read, write and manage access\n  - `Code` - Full\n  - Please take a look at the [terraform provider](https://registry.terraform.io/providers/microsoft/azuredevops/latest/docs/guides/authenticating_using_the_personal_access_token#create-a-personal-access-token)\n    for more explanation.\n  - Azure DevOps only supports RSA keys. Please see\n    [documentation](https://learn.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops#set-up-ssh-key-authentication)\n    for how to set up SSH key authentication.\n  - When using in CI, create a test user and use the test user's PAT and SSH key\n    for all Azure DevOps interactions. To grant the test user access in Azure\n    DevOps:\n    - Go to `Organization Settings` on the sidebar of the organization page.\n    - Under `General` > `Users`, click on `Add User` and input the user's email,\n      select `Access Level` of `Basic`.\n    - Go to `Security` > `Permissions`, click on the `User` tab.\n    - For the invited user, set the following permissions to `Allow`:\n      - `General: Create new project`.\n    - The user will get an email invitation and would need to create a Microsoft\n      account if they don't have one yet.\n\n**NOTE:** To use Service Principal (for example in CI environment), set the\n`ARM-*` variables in `.env`, source it and authenticate Azure CLI with:\n```console\n$ az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID\n```\n\n### Permissions\n\nFollowing permissions are needed for provisioning the infrastructure and running\nthe tests:\n- `Microsoft.Kubernetes/*`\n- `Microsoft.Resources/*`\n- `Microsoft.Authorization/roleAssignments/{Read,Write,Delete}`\n- `Microsoft.ContainerRegistry/*`\n- `Microsoft.ContainerService/*`\n- `Microsoft.KeyVault/*`\n- `Microsoft.EventHub/*`\n\n### IAM and CI setup\n\nTo create the necessary IAM role with all the permissions, set up CI secrets and\nvariables using\n[azure-gh-actions](https://github.com/fluxcd/test-infra/tree/main/tf-modules/azure/github-actions)\nuse the terraform configuration below. Please make sure all the requirements of\nazure-gh-actions are followed before running it.\n\n**NOTE:** When running the following for a repo under an organization, set the\nenvironment variable `GITHUB_ORGANIZATION` if setting the `owner` in the\n`github` provider doesn't work.\n\n```hcl\nprovider \"github\" {\n  owner = \"fluxcd\"\n}\n\nresource \"tls_private_key\" \"privatekey\" {\n  algorithm = \"RSA\"\n  rsa_bits  = 4096\n}\n\nmodule \"azure_gh_actions\" {\n  source = \"git::https://github.com/fluxcd/test-infra.git//tf-modules/azure/github-actions\"\n\n  azure_owners          = [\"owner-id-1\", \"owner-id-2\"]\n  azure_app_name        = \"flux2-e2e\"\n  azure_app_description = \"flux2 e2e\"\n  azure_app_secret_name = \"flux2-e2e\"\n  azure_permissions = [\n    \"Microsoft.Kubernetes/*\",\n    \"Microsoft.Resources/*\",\n    \"Microsoft.Authorization/roleAssignments/Read\",\n    \"Microsoft.Authorization/roleAssignments/Write\",\n    \"Microsoft.Authorization/roleAssignments/Delete\",\n    \"Microsoft.ContainerRegistry/*\",\n    \"Microsoft.ContainerService/*\",\n    \"Microsoft.KeyVault/*\",\n    \"Microsoft.EventHub/*\"\n  ]\n  azure_location = \"eastus\"\n\n  github_project = \"flux2\"\n\n  github_secret_client_id_name       = \"AZ_ARM_CLIENT_ID\"\n  github_secret_client_secret_name   = \"AZ_ARM_CLIENT_SECRET\"\n  github_secret_subscription_id_name = \"AZ_ARM_SUBSCRIPTION_ID\"\n  github_secret_tenant_id_name       = \"AZ_ARM_TENANT_ID\"\n\n  github_secret_custom = {\n    \"TF_VAR_azuredevops_org\"         = \"<azuredevops-org-name>\",\n    \"TF_VAR_azuredevops_pat\"         = \"<azuredevops-pat>\",\n    \"AZURE_GITREPO_SSH_CONTENTS\"     = base64encode(tls_private_key.privatekey.private_key_openssh),\n    \"AZURE_GITREPO_SSH_PUB_CONTENTS\" = base64encode(tls_private_key.privatekey.public_key_openssh)\n  }\n}\n\noutput \"publickey\" {\n  value = tls_private_key.privatekey.public_key_openssh\n}\n```\n\nCopy the `publickey` output printed after applying, or run `terraform output` to\nprint it again, and add it in the Azure DevOps SSH public keys under the user\naccount that'll be used by flux in the tests.\n\n**NOTE:** The environment variables used above are for the GitHub workflow that\nruns the tests. Change the variable names if needed accordingly.\n\n## GCP\n\n### Architecture\n\nThe [gcp](./terraform/gcp) terraform files create the GKE cluster and related resources to run the tests. It creates:\n- A Google Container Registry and Artifact Registry\n- A Google Kubernetes Cluster\n- Two Google Cloud Source Repositories\n- A Google Pub/Sub Topic and a subscription to the service that would be used in the tests\n\nNote: It doesn't create Google KMS keyrings and crypto keys because these cannot be destroyed. Instead, you have\nto pass in the crypto key and keyring that would be used to test the sops encryption in Flux. Please see `.env.sample`\nfor the terraform variables\n\n### Requirements\n\n- GCP account with an active project to be able to create GKE and GCR, and permission to assign roles.\n- Existing GCP KMS keyring and crypto key.\n  - [Create a Keyring](https://cloud.google.com/kms/docs/create-key-ring) in\n    `global` location.\n  - [Create a Crypto Key](https://cloud.google.com/kms/docs/create-key) with\n    symmetric algorithm for encryption and decryption, and software based\n    protection level.\n- gcloud CLI, need to be logged in using `gcloud auth login` as a User (not a\n  Service Account), configure application default credentials with `gcloud auth\n  application-default login` and docker credential helper with `gcloud auth configure-docker`.\n\n  **NOTE:** To use Service Account (for example in CI environment), set\n  `GOOGLE_APPLICATION_CREDENTIALS` variable in `.env` with the path to the JSON\n  key file, source it and authenticate gcloud CLI with:\n  ```console\n  $ gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS\n  ```\n  Depending on the Container/Artifact Registry host used in the test, authenticate\n  docker accordingly\n  ```console\n  $ gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://us-central1-docker.pkg.dev\n  ```\n  In this case, the GCP client in terraform uses the Service Account to\n  authenticate and the gcloud CLI is used only to authenticate with Google\n  Container Registry and Google Artifact Registry.\n\n  **NOTE FOR CI USAGE:** When saving the JSON key file as a CI secret, compress\n  the file content with\n  ```console\n  $ cat key.json | jq -r tostring\n  ```\n  to prevent aggressive masking in the logs. Refer\n  [aggressive replacement in logs](https://github.com/google-github-actions/auth/blob/v1.1.0/docs/TROUBLESHOOTING.md#aggressive--replacement-in-logs)\n  for more details.\n- Register [SSH Keys with Google Cloud](https://cloud.google.com/source-repositories/docs/authentication#ssh)\n  - Google Cloud supports these three SSH key types: RSA (only for keys with\n    more than 2048 bits), ECDSA and ED25519.\n  - The SSH user doesn't have to be a member of the GCP project. The terraform\n    setup will grant the user permissions to the repository. Visit\n    https://source.cloud.google.com, login or create a GCP account with the SSH\n    user's email address and add SSH keys in the account. Set this email as the\n    value for the environment variable `TF_VAR_gcp_email` in `.env` file to be\n    used as a terraform variable.\n\n  **Note:** Google doesn't allow a SSH key to be associated with a service\n  account email address. Therefore, there has to be an actual user that the SSH\n  key is registered to.\n\n### Permissions\n\nFollowing roles are needed for provisioning the infrastructure and running the tests:\n\n- Compute Instance Admin (v1) - `roles/compute.instanceAdmin.v1`\n- Kubernetes Engine Admin - `roles/container.admin`\n- Service Account User - `roles/iam.serviceAccountUser`\n- Service Account Token Creator - `roles/iam.serviceAccountTokenCreator`\n- Artifact Registry Administrator - `roles/artifactregistry.admin`\n- Artifact Registry Repository Administrator - `roles/artifactregistry.repoAdmin`\n- Cloud KMS Admin - `roles/cloudkms.admin`\n- Cloud KMS CryptoKey Encrypter - `roles/cloudkms.cryptoKeyEncrypter`\n- Source Repository Administrator - `roles/source.admin`\n- Pub/Sub Admin - `roles/pubsub.admin`\n\n### IAM and CI setup\n\nTo create the necessary IAM role with all the permissions, set up CI secrets and\nvariables using\n[gcp-gh-actions](https://github.com/fluxcd/test-infra/tree/main/tf-modules/gcp/github-actions)\nuse the terraform configuration below. Please make sure all the requirements of\ngcp-gh-actions are followed before running it.\n\n**NOTE:** When running the following for a repo under an organization, set the\nenvironment variable `GITHUB_ORGANIZATION` if setting the `owner` in the\n`github` provider doesn't work.\n\n```hcl\nprovider \"google\" {}\n\nprovider \"github\" {\n  owner = \"fluxcd\"\n}\n\nresource \"tls_private_key\" \"privatekey\" {\n  algorithm = \"RSA\"\n  rsa_bits  = 4096\n}\n\nmodule \"gcp_gh_actions\" {\n  source = \"git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/github-actions\"\n\n  gcp_service_account_id          = \"flux2-e2e-test\"\n  gcp_service_account_name        = \"flux2-e2e-test\"\n  gcp_service_account_description = \"For running fluxcd/flux2 e2e tests.\"\n  gcp_roles = [\n    \"roles/compute.instanceAdmin.v1\",\n    \"roles/container.admin\",\n    \"roles/iam.serviceAccountUser\",\n    \"roles/iam.serviceAccountTokenCreator\",\n    \"roles/artifactregistry.admin\",\n    \"roles/artifactregistry.repoAdmin\",\n    \"roles/cloudkms.admin\",\n    \"roles/cloudkms.cryptoKeyEncrypter\",\n    \"roles/source.admin\",\n    \"roles/pubsub.admin\"\n  ]\n\n  github_project = \"flux2\"\n\n  github_secret_credentials_name = \"FLUX2_E2E_GOOGLE_CREDENTIALS\"\n\n  github_secret_custom = {\n    \"TF_VAR_gcp_keyring\"           = \"<keyring-name>\",\n    \"TF_VAR_gcp_crypto_key\"        = \"<key-name>\",\n    \"TF_VAR_gcp_email\"             = \"<email>\",\n    \"GCP_GITREPO_SSH_CONTENTS\"     = base64encode(tls_private_key.privatekey.private_key_openssh),\n    \"GCP_GITREPO_SSH_PUB_CONTENTS\" = base64encode(tls_private_key.privatekey.public_key_openssh)\n  }\n}\n\noutput \"publickey\" {\n  value = tls_private_key.privatekey.public_key_openssh\n}\n```\n\nCopy the `publickey` output printed after applying, or run `terraform output` to\nprint it again, and add it in the Google Source Repository SSH public keys under\nthe user account with email address referred in `TF_VAR_gcp_email` above.\n\n**NOTE:** The environment variables used above are for the GitHub workflow that\nruns the tests. Change the variable names if needed accordingly.\n\n## Tests\n\nEach test run is initiated by running `terraform apply` in the provider's terraform directory e.g terraform apply,\nit does this by using the [tftestenv package](https://github.com/fluxcd/test-infra/blob/main/tftestenv/testenv.go)\nwithin the `fluxcd/test-infra` repository. It then reads the output of the Terraform to get information needed\nfor the tests like the kubernetes client ID, the cloud repository urls, the key vault ID etc. This means that\na lot of the communication with the cloud provider API is offset to Terraform instead of requiring it to be implemented in the test.\n\nThe following tests are currently implemented:\n\n- Flux can be successfully installed on the cluster using the Flux CLI\n- source-controller can clone cloud provider repositories (Azure DevOps, Google Cloud Source Repositories) (https+ssh)\n- image-reflector-controller can list tags from provider container Registry image repositories\n- kustomize-controller can decrypt secrets using SOPS and provider key vault\n- image-automation-controller can create branches and push to cloud repositories (https+ssh)\n- source-controller can pull charts from cloud provider container registry Helm repositories\n- notification-controller can forward events to cloud Events Service(EventHub for Azure and Google Pub/Sub)\n\nThe following tests are run only for Azure since it is supported in the notification-controller:\n\n- notification-controller can send commit status to Azure DevOps\n\n### Running tests locally\n\n1. Ensure that you have the Flux CLI binary that is to be tested built and ready. You can build it by running\n   `make build` at the root of this repository. The binary is located at `./bin` directory at the root and by default\n   this is where the Makefile copies the binary for the tests from. If you have it in a different location, you can set it\n   with the `FLUX_BINARY` variable\n2. Copy `.env.sample` to `.env` and add the values for the different variables for the provider that you are running the tests for. \n3. Run `make test-<provider>`, setting the location of the flux binary with `FLUX_BINARY` variable\n\n```console\n$ make test-azure\nmake test PROVIDER_ARG=\"-provider azure\"\n# These two versions of podinfo are pushed to the cloud registry and used in tests for ImageUpdateAutomation\nmkdir -p build\ncp ../../bin/flux build/flux\ndocker pull ghcr.io/stefanprodan/podinfo:6.0.0\n6.0.0: Pulling from stefanprodan/podinfo\nDigest: sha256:e7eeab287181791d36c82c904206a845e30557c3a4a66a8143fa1a15655dae97\nStatus: Image is up to date for ghcr.io/stefanprodan/podinfo:6.0.0\nghcr.io/stefanprodan/podinfo:6.0.0\ndocker pull ghcr.io/stefanprodan/podinfo:6.0.1\n6.0.1: Pulling from stefanprodan/podinfo\nDigest: sha256:1169f220a670cf640e45e1a7ac42dc381a441e9d4b7396432cadb75beb5b5d68\nStatus: Image is up to date for ghcr.io/stefanprodan/podinfo:6.0.1\nghcr.io/stefanprodan/podinfo:6.0.1\ngo test -timeout 60m -v ./ -existing -provider azure --tags=integration\n2023/03/24 02:32:25 Setting up azure e2e test infrastructure\n2023/03/24 02:32:25 Terraform binary:  /usr/local/bin/terraform\n2023/03/24 02:32:25 Init Terraform\n....[some output has been cut out]\n2023/03/24 02:39:33 helm repository condition not ready\n--- PASS: TestACRHelmRelease (15.31s)\n=== RUN   TestKeyVaultSops\n--- PASS: TestKeyVaultSops (15.98s)\nPASS\n2023/03/24 02:40:12 Destroying environment...\nok      github.com/fluxcd/flux2/tests/integration       947.341s\n```\n\nIn the above, the test created a build directory build/ and the flux cli binary is copied build/flux. It would be used\nto bootstrap Flux on the cluster. You can configure the location of the Flux CLI binary by setting the FLUX_BINARY variable.\nWe also pull two version of `ghcr.io/stefanprodan/podinfo` image. These images are pushed to the cloud provider's\nContainer Registry and used to test `ImageRepository` and `ImageUpdateAutomation`. The terraform resources get created\nand the tests are run.\n\nIf not configured explicitly to retain the infrastructure, at the end of the\ntest, the test infrastructure is deleted. In case of any failure due to which\nthe resources don't get deleted, the `make destroy-*` commands can be run for\nthe respective provider. This will run terraform destroy in the respective\nprovider's terraform configuration directory. This can be used to quickly\ndestroy the infrastructure without going through the provision-test-destroy\nsteps.\n\n### Debugging the tests\n\nFor debugging environment provisioning, enable verbose output with `-verbose` test flag.\n\n```sh\nmake test-azure GO_TEST_ARGS=\"-verbose\"\n```\n\nThe test environment is destroyed at the end by default. Run the tests with -retain flag to retain the created test infrastructure.\n\n```sh\nmake test-azure GO_TEST_ARGS=\"-retain\"\n```\nThe tests require the infrastructure state to be clean. For re-running the tests with a retained infrastructure, set -existing flag.\n\n```sh\nmake test-azure GO_TEST_ARGS=\"-retain -existing\"\n```\n\nTo delete an existing infrastructure created with -retain flag:\n\n```sh\nmake test-azure GO_TEST_ARGS=\"-existing\"\n```\n\nTo debug issues on the cluster created by the test (provided you passed in the `-retain` flag):\n\n```sh\nexport KUBECONFIG=./build/kubeconfig\nkubectl get pods\n```\n"
  },
  {
    "path": "tests/integration/azure_specific_test.go",
    "content": "//go:build azure\n// +build azure\n\n/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tgiturls \"github.com/chainguard-dev/git-urls\"\n\t\"github.com/microsoft/azure-devops-go-api/azuredevops\"\n\t\"github.com/microsoft/azure-devops-go-api/azuredevops/git\"\n\t. \"github.com/onsi/gomega\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tnotiv1 \"github.com/fluxcd/notification-controller/api/v1\"\n\tnotiv1beta3 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nfunc TestAzureDevOpsCommitStatus(t *testing.T) {\n\tg := NewWithT(t)\n\n\tctx := context.TODO()\n\tbranchName := \"commit-status\"\n\ttestID := branchName + randStringRunes(5)\n\tmanifest := `apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: foobar`\n\n\trepoUrl := getTransportURL(cfg.applicationRepository)\n\ttmpDir := t.TempDir()\n\tc, err := getRepository(ctx, tmpDir, repoUrl, defaultBranch, cfg.defaultAuthOpts)\n\tg.Expect(err).ToNot(HaveOccurred())\n\tfiles := make(map[string]io.Reader)\n\tfiles[\"configmap.yaml\"] = strings.NewReader(manifest)\n\terr = commitAndPushAll(ctx, c, files, branchName)\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\tmodifyKsSpec := func(spec *kustomizev1.KustomizationSpec) {\n\t\tspec.HealthChecks = []meta.NamespacedObjectKindReference{\n\t\t\t{\n\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\tKind:       \"ConfigMap\",\n\t\t\t\tName:       \"foobar\",\n\t\t\t\tNamespace:  testID,\n\t\t\t},\n\t\t}\n\t}\n\terr = setUpFluxConfig(ctx, testID, nsConfig{\n\t\tref: &sourcev1.GitRepositoryRef{\n\t\t\tBranch: branchName,\n\t\t},\n\t\trepoURL:      repoUrl,\n\t\tpath:         \"./\",\n\t\tmodifyKsSpec: modifyKsSpec,\n\t})\n\tg.Expect(err).ToNot(HaveOccurred())\n\tt.Cleanup(func() {\n\t\terr := tearDownFluxConfig(ctx, testID)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"failed to delete resources in '%s' namespace: %s\", testID, err)\n\t\t}\n\t})\n\tt.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })\n\n\tg.Eventually(func() bool {\n\t\terr := verifyGitAndKustomization(ctx, testEnv, testID, testID)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}, testTimeout, testInterval)\n\n\tsecret := corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"azuredevops-token\",\n\t\t\tNamespace: testID,\n\t\t},\n\t\tStringData: map[string]string{\n\t\t\t\"token\": cfg.gitPat,\n\t\t},\n\t}\n\tg.Expect(testEnv.Create(ctx, &secret)).To(Succeed())\n\tdefer testEnv.Delete(ctx, &secret)\n\n\tprovider := notiv1beta3.Provider{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"azuredevops\",\n\t\t\tNamespace: testID,\n\t\t},\n\t\tSpec: notiv1beta3.ProviderSpec{\n\t\t\tType:    \"azuredevops\",\n\t\t\tAddress: repoUrl,\n\t\t\tSecretRef: &meta.LocalObjectReference{\n\t\t\t\tName: \"azuredevops-token\",\n\t\t\t},\n\t\t},\n\t}\n\tg.Expect(testEnv.Create(ctx, &provider)).To(Succeed())\n\tdefer testEnv.Delete(ctx, &provider)\n\n\talert := notiv1beta3.Alert{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"azuredevops\",\n\t\t\tNamespace: testID,\n\t\t},\n\t\tSpec: notiv1beta3.AlertSpec{\n\t\t\tProviderRef: meta.LocalObjectReference{\n\t\t\t\tName: provider.Name,\n\t\t\t},\n\t\t\tEventSources: []notiv1.CrossNamespaceObjectReference{\n\t\t\t\t{\n\t\t\t\t\tKind:      \"Kustomization\",\n\t\t\t\t\tName:      testID,\n\t\t\t\t\tNamespace: testID,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tg.Expect(testEnv.Create(ctx, &alert)).To(Succeed())\n\tdefer testEnv.Delete(ctx, &alert)\n\n\turl, err := ParseAzureDevopsURL(repoUrl)\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\trev, err := c.Head()\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\tconnection := azuredevops.NewPatConnection(url.OrgURL, cfg.gitPat)\n\tclient, err := git.NewClient(ctx, connection)\n\tg.Expect(err).ToNot(HaveOccurred())\n\tgetArgs := git.GetStatusesArgs{\n\t\tProject:      &url.Project,\n\t\tRepositoryId: &url.Repo,\n\t\tCommitId:     &rev,\n\t}\n\tg.Eventually(func() bool {\n\t\tstatuses, err := client.GetStatuses(ctx, getArgs)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tif len(*statuses) != 1 {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}, 500*time.Second, 5*time.Second)\n}\n\ntype AzureDevOpsURL struct {\n\tOrgURL  string\n\tProject string\n\tRepo    string\n}\n\n// TODO(somtochiama): move this into fluxcd/pkg and reuse in NC\nfunc ParseAzureDevopsURL(s string) (AzureDevOpsURL, error) {\n\tvar args AzureDevOpsURL\n\tu, err := giturls.Parse(s)\n\tif err != nil {\n\t\treturn args, nil\n\t}\n\n\tscheme := u.Scheme\n\tif u.Scheme == \"ssh\" {\n\t\tscheme = \"https\"\n\t}\n\n\tid := strings.TrimLeft(u.Path, \"/\")\n\tid = strings.TrimSuffix(id, \".git\")\n\n\tcomp := strings.Split(id, \"/\")\n\tif len(comp) != 4 {\n\t\treturn args, fmt.Errorf(\"invalid repository id %q\", id)\n\t}\n\n\targs = AzureDevOpsURL{\n\t\tOrgURL:  fmt.Sprintf(\"%s://%s/%s\", scheme, u.Host, comp[0]),\n\t\tProject: comp[1],\n\t\tRepo:    comp[3],\n\t}\n\n\treturn args, nil\n\n}\n"
  },
  {
    "path": "tests/integration/azure_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\teventhub \"github.com/Azure/azure-event-hubs-go/v3\"\n\t\"github.com/fluxcd/pkg/git\"\n\t\"github.com/fluxcd/test-infra/tftestenv\"\n\ttfjson \"github.com/hashicorp/terraform-json\"\n)\n\nconst (\n\tazureDevOpsKnownHosts = \"ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H\"\n)\n\n// createKubeConfigAKS constructs kubeconfig for an AKS cluster from the\n// terraform state output at the given kubeconfig path.\nfunc createKubeConfigAKS(ctx context.Context, state map[string]*tfjson.StateOutput, kcPath string) error {\n\tkubeconfigYaml, ok := state[\"aks_kubeconfig\"].Value.(string)\n\tif !ok || kubeconfigYaml == \"\" {\n\t\treturn fmt.Errorf(\"failed to obtain kubeconfig from tf output\")\n\t}\n\treturn tftestenv.CreateKubeconfigAKS(ctx, kubeconfigYaml, kcPath)\n}\n\nfunc getTestConfigAKS(ctx context.Context, outputs map[string]*tfjson.StateOutput) (*testConfig, error) {\n\tfleetInfraRepository := outputs[\"fleet_infra_repository\"].Value.(map[string]interface{})\n\tapplicationRepository := outputs[\"application_repository\"].Value.(map[string]interface{})\n\n\teventHubSas := outputs[\"event_hub_sas\"].Value.(string)\n\tsharedSopsId := outputs[\"sops_id\"].Value.(string)\n\n\tkustomizeYaml := `\nresources:\n  - gotk-components.yaml\n  - gotk-sync.yaml\npatchesStrategicMerge:\n  - |-\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: kustomize-controller\n      namespace: flux-system\n    spec:\n      template:\n        spec:\n          containers:\n          - name: manager\n            env:\n            - name: AZURE_AUTH_METHOD\n              value: msi\n`\n\n\tprivateKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPath)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%s env variable isn't set\", envVarGitRepoSSHPath)\n\t}\n\tprivateKeyData, err := os.ReadFile(privateKeyFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting azure devops private key, '%s': %w\", privateKeyFile, err)\n\t}\n\n\tpubKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPubPath)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%s env variable isn't set\", envVarGitRepoSSHPubPath)\n\t}\n\tpubKeyData, err := os.ReadFile(pubKeyFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting ssh pubkey '%s', %w\", pubKeyFile, err)\n\t}\n\n\tc := make(chan []byte, 10)\n\tclosefn, err := setupEventHubHandler(ctx, c, eventHubSas)\n\n\tvar notificationCfg = notificationConfig{\n\t\tnotificationChan: c,\n\t\tproviderType:     \"azureeventhub\",\n\t\tcloseChan:        closefn,\n\t\tsecret: map[string]string{\n\t\t\t\"address\": eventHubSas,\n\t\t},\n\t}\n\n\tconfig := &testConfig{\n\t\tdefaultGitTransport: git.HTTP,\n\t\tgitUsername:         git.DefaultPublicKeyAuthUser,\n\t\tgitPat:              outputs[\"azure_devops_access_token\"].Value.(string),\n\t\tgitPrivateKey:       string(privateKeyData),\n\t\tgitPublicKey:        string(pubKeyData),\n\t\tknownHosts:          azureDevOpsKnownHosts,\n\t\tfleetInfraRepository: gitUrl{\n\t\t\thttp: fleetInfraRepository[\"http\"].(string),\n\t\t\tssh:  fleetInfraRepository[\"ssh\"].(string),\n\t\t},\n\t\tapplicationRepository: gitUrl{\n\t\t\thttp: applicationRepository[\"http\"].(string),\n\t\t\tssh:  applicationRepository[\"ssh\"].(string),\n\t\t},\n\t\tnotificationCfg: notificationCfg,\n\t\tsopsArgs:        fmt.Sprintf(\"--azure-kv %s\", sharedSopsId),\n\t\tsopsSecretData: map[string]string{\n\t\t\t\"sops.azure-kv\": fmt.Sprintf(`clientId: %s`, outputs[\"aks_client_id\"].Value.(string)),\n\t\t},\n\t\tkustomizationYaml: kustomizeYaml,\n\t}\n\n\topts, err := authOpts(config.fleetInfraRepository.http, map[string][]byte{\n\t\t\"password\": []byte(config.gitPat),\n\t\t\"username\": []byte(\"git\"),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconfig.defaultAuthOpts = opts\n\n\treturn config, nil\n}\n\n// registryLoginACR logs into the Azure Container Registries using the\n// provider's CLI tools and returns the test repositories.\nfunc registryLoginACR(ctx context.Context, output map[string]*tfjson.StateOutput) (string, error) {\n\t// NOTE: ACR registry accept dynamic repository creation by just pushing a\n\t// new image with a new repository name.\n\tregistryURL := output[\"acr_url\"].Value.(string)\n\tif err := tftestenv.RegistryLoginACR(ctx, registryURL); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn registryURL, nil\n}\n\nfunc setupEventHubHandler(ctx context.Context, c chan []byte, eventHubSas string) (func(), error) {\n\thub, err := eventhub.NewHubFromConnectionString(eventHubSas)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thandler := func(ctx context.Context, event *eventhub.Event) error {\n\t\tc <- event.Data\n\t\treturn nil\n\t}\n\truntimeInfo, err := hub.GetRuntimeInformation(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlistenerHandler, err := hub.Receive(ctx, runtimeInfo.PartitionIDs[0], handler, eventhub.ReceiveWithLatestOffset())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tclosefn := func() {\n\t\tlistenerHandler.Close(ctx)\n\t\thub.Close(ctx)\n\t}\n\n\treturn closefn, nil\n}\n"
  },
  {
    "path": "tests/integration/flux_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fluxcd/pkg/git\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\t. \"github.com/onsi/gomega\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n)\n\nfunc TestFluxInstallation(t *testing.T) {\n\tg := NewWithT(t)\n\tctx := context.TODO()\n\tt.Cleanup(func() { dumpDiagnostics(t, ctx, \"flux-system\") })\n\tg.Eventually(func() bool {\n\t\terr := verifyGitAndKustomization(ctx, testEnv.Client, \"flux-system\", \"flux-system\")\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}, 60*time.Second, 5*time.Second)\n}\n\nfunc TestRepositoryCloning(t *testing.T) {\n\tctx := context.TODO()\n\tbranchName := \"feature/branch\"\n\ttagName := \"v1\"\n\n\tg := NewWithT(t)\n\n\ttype testStruct struct {\n\t\tname      string\n\t\trefType   string\n\t\tcloneType git.TransportType\n\t}\n\n\ttests := []testStruct{\n\t\t{\n\t\t\tname:      \"ssh-feature-branch\",\n\t\t\trefType:   \"branch\",\n\t\t\tcloneType: git.SSH,\n\t\t},\n\t\t{\n\t\t\tname:      \"ssh-v1\",\n\t\t\trefType:   \"tag\",\n\t\t\tcloneType: git.SSH,\n\t\t},\n\t}\n\n\t// Not all cloud providers have repositories that support authentication with an accessToken\n\t// we don't run http tests for these.\n\tif cfg.gitPat != \"\" {\n\t\thttpTests := []testStruct{\n\t\t\t{\n\t\t\t\tname:      \"https-feature-branch\",\n\t\t\t\trefType:   \"branch\",\n\t\t\t\tcloneType: git.HTTP,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:      \"https-v1\",\n\t\t\t\trefType:   \"tag\",\n\t\t\t\tcloneType: git.HTTP,\n\t\t\t},\n\t\t}\n\n\t\ttests = append(tests, httpTests...)\n\t}\n\n\tt.Log(\"Creating application sources\")\n\turl := getTransportURL(cfg.applicationRepository)\n\ttmpDir := t.TempDir()\n\tclient, err := getRepository(ctx, tmpDir, url, defaultBranch, cfg.defaultAuthOpts)\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\tfiles := make(map[string]io.Reader)\n\tfor _, tt := range tests {\n\t\tmanifest := `apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: foobar\n    `\n\t\tname := fmt.Sprintf(\"cloning-test/%s/configmap.yaml\", tt.name)\n\t\tfiles[name] = strings.NewReader(manifest)\n\t}\n\n\terr = commitAndPushAll(ctx, client, files, branchName)\n\tg.Expect(err).ToNot(HaveOccurred())\n\terr = createTagAndPush(ctx, client, branchName, tagName)\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\tt.Log(\"Verifying application-gitops namespaces\")\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tg := NewWithT(t)\n\t\t\tctx := context.TODO()\n\t\t\tref := &sourcev1.GitRepositoryRef{\n\t\t\t\tBranch: branchName,\n\t\t\t}\n\t\t\tif tt.refType == \"tag\" {\n\t\t\t\tref = &sourcev1.GitRepositoryRef{\n\t\t\t\t\tTag: tagName,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\turl := cfg.applicationRepository.http\n\t\t\tif tt.cloneType == git.SSH {\n\t\t\t\turl = cfg.applicationRepository.ssh\n\t\t\t}\n\n\t\t\ttestID := fmt.Sprintf(\"%s-%s\", tt.name, randStringRunes(5))\n\t\t\terr := setUpFluxConfig(ctx, testID, nsConfig{\n\t\t\t\trepoURL:    url,\n\t\t\t\tref:        ref,\n\t\t\t\tprotocol:   tt.cloneType,\n\t\t\t\tobjectName: testID,\n\t\t\t\tpath:       fmt.Sprintf(\"./cloning-test/%s\", tt.name),\n\t\t\t})\n\t\t\tg.Expect(err).ToNot(HaveOccurred())\n\t\t\tt.Cleanup(func() {\n\t\t\t\terr := tearDownFluxConfig(ctx, testID)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Logf(\"failed to delete resources in '%s' namespace: %s\", tt.name, err)\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })\n\n\t\t\tg.Eventually(func() bool {\n\t\t\t\terr := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t}, 120*time.Second, 5*time.Second).Should(BeTrue())\n\n\t\t\t// Wait for configmap to be deployed\n\t\t\tg.Eventually(func() bool {\n\t\t\t\tnn := types.NamespacedName{Name: \"foobar\", Namespace: testID}\n\t\t\t\tcm := &corev1.ConfigMap{}\n\t\t\t\terr = testEnv.Get(ctx, nn, cm)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\n\t\t\t\treturn true\n\t\t\t}, 120*time.Second, 5*time.Second).Should(BeTrue())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "tests/integration/gcp_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"cloud.google.com/go/pubsub\"\n\ttfjson \"github.com/hashicorp/terraform-json\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/fluxcd/pkg/git\"\n\t\"github.com/fluxcd/test-infra/tftestenv\"\n)\n\nconst (\n\tgcpSourceRepoKnownHosts = \"[source.developers.google.com]:2022 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBB5Iy4/cq/gt/fPqe3uyMy4jwv1Alc94yVPxmnwNhBzJqEV5gRPiRk5u4/JJMbbu9QUVAguBABxL7sBZa5PH/xY=\"\n)\n\n// createKubeConfigGKE constructs kubeconfig for a GKE cluster from the\n// terraform state output at the given kubeconfig path.\nfunc createKubeConfigGKE(ctx context.Context, state map[string]*tfjson.StateOutput, kcPath string) error {\n\tkubeconfigYaml, ok := state[\"gke_kubeconfig\"].Value.(string)\n\tif !ok || kubeconfigYaml == \"\" {\n\t\treturn fmt.Errorf(\"failed to obtain kubeconfig from tf output\")\n\t}\n\treturn tftestenv.CreateKubeconfigGKE(ctx, kubeconfigYaml, kcPath)\n}\n\n// registryLoginGCR logs into the Artifact registries using the gcloud\n// and returns a list of test repositories.\nfunc registryLoginGCR(ctx context.Context, output map[string]*tfjson.StateOutput) (string, error) {\n\tproject := output[\"gcp_project_id\"].Value.(string)\n\tregion := output[\"gcp_region\"].Value.(string)\n\trepositoryID := output[\"artifact_registry_id\"].Value.(string)\n\tartifactRegistryURL, artifactRepoURL := tftestenv.GetGoogleArtifactRegistryAndRepository(project, region, repositoryID)\n\tif err := tftestenv.RegistryLoginGCR(ctx, artifactRegistryURL); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn artifactRepoURL, nil\n}\n\nfunc getTestConfigGKE(ctx context.Context, outputs map[string]*tfjson.StateOutput) (*testConfig, error) {\n\tsharedSopsId := outputs[\"sops_id\"].Value.(string)\n\n\tprivateKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPath)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%s env variable isn't set\", envVarGitRepoSSHPath)\n\t}\n\tprivateKeyData, err := os.ReadFile(privateKeyFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting gcp source repositories private key, '%s': %w\", privateKeyFile, err)\n\t}\n\n\tpubKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPubPath)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%s env variable isn't set\", envVarGitRepoSSHPubPath)\n\t}\n\tpubKeyData, err := os.ReadFile(pubKeyFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting ssh pubkey '%s', %w\", pubKeyFile, err)\n\t}\n\n\tc := make(chan []byte, 10)\n\tprojectID := outputs[\"gcp_project_id\"].Value.(string)\n\ttopicID := outputs[\"pubsub_topic\"].Value.(string)\n\n\tfn, err := setupPubSubReceiver(ctx, c, projectID, topicID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar notificationCfg = notificationConfig{\n\t\tproviderType:     \"googlepubsub\",\n\t\tproviderChannel:  topicID,\n\t\tnotificationChan: c,\n\t\tcloseChan:        fn,\n\t\tsecret: map[string]string{\n\t\t\t\"address\": projectID,\n\t\t},\n\t}\n\n\tconfig := &testConfig{\n\t\tdefaultGitTransport: git.SSH,\n\t\tgitUsername:         \"git\",\n\t\tgitPrivateKey:       string(privateKeyData),\n\t\tgitPublicKey:        string(pubKeyData),\n\t\tknownHosts:          gcpSourceRepoKnownHosts,\n\t\tfleetInfraRepository: gitUrl{\n\t\t\tssh: outputs[\"fleet_infra_repository\"].Value.(string),\n\t\t},\n\t\tapplicationRepository: gitUrl{\n\t\t\tssh: outputs[\"application_repository\"].Value.(string),\n\t\t},\n\t\tnotificationCfg: notificationCfg,\n\t\tsopsArgs:        fmt.Sprintf(\"--gcp-kms %s\", sharedSopsId),\n\t}\n\n\topts, err := authOpts(config.fleetInfraRepository.ssh, map[string][]byte{\n\t\t\"identity\":    []byte(config.gitPrivateKey),\n\t\t\"known_hosts\": []byte(config.knownHosts),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconfig.defaultAuthOpts = opts\n\n\t// In Azure, the repository is initialized with a default branch through\n\t// terraform. We have to do it manually here for GCP to prevent errors\n\t// when trying to clone later. We only need to do it for the application repository\n\t// since flux bootstrap pushes to the main branch.\n\tfiles := make(map[string]io.Reader)\n\tfiles[\"README.md\"] = strings.NewReader(\"# Flux test repo\")\n\ttmpDir, err := os.MkdirTemp(\"\", \"*-flux-test\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer os.RemoveAll(tmpDir)\n\n\tclient, err := getRepository(context.Background(), tmpDir, config.applicationRepository.ssh, defaultBranch, config.defaultAuthOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = commitAndPushAll(context.Background(), client, files, defaultBranch)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn config, nil\n}\n\nfunc setupPubSubReceiver(ctx context.Context, c chan []byte, projectID string, topicID string) (func(), error) {\n\tnewCtx, cancel := context.WithCancel(ctx)\n\tpubsubClient, err := pubsub.NewClient(newCtx, projectID)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, fmt.Errorf(\"error creating pubsub client: %s\", err)\n\t}\n\n\tsub := pubsubClient.Subscription(topicID)\n\tgo func() {\n\t\terr = sub.Receive(ctx, func(ctx context.Context, message *pubsub.Message) {\n\t\t\tc <- message.Data\n\t\t\tmessage.Ack()\n\t\t})\n\t\tif err != nil && status.Code(err) != codes.Canceled {\n\t\t\tlog.Printf(\"error receiving message in subscription: %s\\n\", err)\n\t\t\treturn\n\t\t}\n\t}()\n\n\treturn func() {\n\t\tcancel()\n\t\tpubsubClient.Close()\n\t}, nil\n}\n"
  },
  {
    "path": "tests/integration/go.mod",
    "content": "module github.com/fluxcd/flux2/tests/integration\n\ngo 1.26.0\n\nrequire (\n\tcloud.google.com/go/pubsub v1.50.1\n\tgithub.com/Azure/azure-event-hubs-go/v3 v3.6.2\n\tgithub.com/chainguard-dev/git-urls v1.0.2\n\tgithub.com/fluxcd/helm-controller/api v1.4.5\n\tgithub.com/fluxcd/image-automation-controller/api v1.0.4\n\tgithub.com/fluxcd/image-reflector-controller/api v1.0.4\n\tgithub.com/fluxcd/kustomize-controller/api v1.7.3\n\tgithub.com/fluxcd/notification-controller/api v1.7.5\n\tgithub.com/fluxcd/pkg/apis/event v0.25.0\n\tgithub.com/fluxcd/pkg/apis/meta v1.26.0\n\tgithub.com/fluxcd/pkg/git v0.46.0\n\tgithub.com/fluxcd/pkg/runtime v0.103.0\n\tgithub.com/fluxcd/source-controller/api v1.7.4\n\tgithub.com/fluxcd/test-infra/tftestenv v0.0.0-20250626232827-e0ca9c3f8d7b\n\tgithub.com/go-git/go-git/v5 v5.16.5\n\tgithub.com/google/go-containerregistry v0.20.7\n\tgithub.com/hashicorp/terraform-exec v0.24.0\n\tgithub.com/hashicorp/terraform-json v0.27.2\n\tgithub.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5\n\tgithub.com/onsi/gomega v1.39.0\n\tgoogle.golang.org/grpc v1.77.0\n\tk8s.io/api v0.35.2\n\tk8s.io/apimachinery v0.35.2\n\tk8s.io/client-go v0.35.2\n\tsigs.k8s.io/controller-runtime v0.23.3\n)\n\nrequire (\n\tcloud.google.com/go v0.121.6 // indirect\n\tcloud.google.com/go/auth v0.16.4 // 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/pubsub/v2 v2.0.0 // indirect\n\tdario.cat/mergo v1.0.1 // indirect\n\tgithub.com/Azure/azure-amqp-common-go/v4 v4.2.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect\n\tgithub.com/Azure/go-amqp v1.4.0 // indirect\n\tgithub.com/Azure/go-autorest v14.2.0+incompatible // indirect\n\tgithub.com/Azure/go-autorest/autorest v0.11.30 // indirect\n\tgithub.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect\n\tgithub.com/Azure/go-autorest/autorest/date v0.3.1 // indirect\n\tgithub.com/Azure/go-autorest/autorest/to v0.4.1 // indirect\n\tgithub.com/Azure/go-autorest/autorest/validation v0.3.2 // indirect\n\tgithub.com/Azure/go-autorest/logger v0.2.2 // indirect\n\tgithub.com/Azure/go-autorest/tracing v0.6.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/Microsoft/go-winio v0.6.2 // indirect\n\tgithub.com/ProtonMail/go-crypto v1.3.0 // indirect\n\tgithub.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect\n\tgithub.com/cloudflare/circl v1.6.3 // indirect\n\tgithub.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect\n\tgithub.com/cyphar/filepath-securejoin v0.6.1 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/devigned/tab v0.1.1 // indirect\n\tgithub.com/docker/cli v29.0.3+incompatible // indirect\n\tgithub.com/docker/distribution v2.8.3+incompatible // indirect\n\tgithub.com/docker/docker-credential-helpers v0.9.3 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.12.2 // indirect\n\tgithub.com/emirpasic/gods v1.18.1 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.9.11 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fluxcd/pkg/apis/acl v0.9.0 // indirect\n\tgithub.com/fluxcd/pkg/apis/kustomize v1.16.0 // indirect\n\tgithub.com/fluxcd/pkg/ssh v0.24.0 // indirect\n\tgithub.com/fluxcd/pkg/version v0.14.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect\n\tgithub.com/go-git/go-billy/v5 v5.7.0 // 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.1 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.0 // indirect\n\tgithub.com/go-openapi/swag v0.23.1 // indirect\n\tgithub.com/golang-jwt/jwt/v4 v4.5.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect\n\tgithub.com/google/gnostic-models v0.7.0 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // 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/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/go-retryablehttp v0.7.8 // indirect\n\tgithub.com/hashicorp/go-version v1.7.0 // indirect\n\tgithub.com/hashicorp/hc-install v0.9.2 // indirect\n\tgithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/jpillora/backoff v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/kevinburke/ssh_config v1.2.0 // indirect\n\tgithub.com/klauspost/compress v1.18.1 // indirect\n\tgithub.com/mailru/easyjson v0.9.0 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.1 // indirect\n\tgithub.com/pjbgf/sha1cd v0.4.0 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/skeema/knownhosts v1.3.1 // indirect\n\tgithub.com/spf13/pflag v1.0.10 // indirect\n\tgithub.com/vbatts/tar-split v0.12.2 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgithub.com/xanzy/ssh-agent v0.3.3 // indirect\n\tgithub.com/zclconf/go-cty v1.16.4 // 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.38.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.38.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/crypto v0.47.0 // indirect\n\tgolang.org/x/mod v0.31.0 // indirect\n\tgolang.org/x/net v0.49.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.40.0 // indirect\n\tgolang.org/x/term v0.39.0 // indirect\n\tgolang.org/x/text v0.33.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgoogle.golang.org/api v0.247.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect\n\tgoogle.golang.org/protobuf v1.36.10 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/warnings.v0 v0.1.2 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/apiextensions-apiserver v0.35.2 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect\n\tk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n)\n"
  },
  {
    "path": "tests/integration/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=\ncloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=\ncloud.google.com/go/auth v0.16.4 h1:fXOAIQmkApVvcIn7Pc2+5J8QTMVbUGLscnSVNl11su8=\ncloud.google.com/go/auth v0.16.4/go.mod h1:j10ncYwjX/g3cdX7GpEzsdM+d+ZNsXAbb6qXA7p1Y5M=\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/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk=\ncloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8=\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=\ncloud.google.com/go/pubsub v1.50.1 h1:fzbXpPyJnSGvWXF1jabhQeXyxdbCIkXTpjXHy7xviBM=\ncloud.google.com/go/pubsub v1.50.1/go.mod h1:6YVJv3MzWJUVdvQXG081sFvS0dWQOdnV+oTo++q/xFk=\ncloud.google.com/go/pubsub/v2 v2.0.0 h1:0qS6mRJ41gD1lNmM/vdm6bR7DQu6coQcVwD+VPf0Bz0=\ncloud.google.com/go/pubsub/v2 v2.0.0/go.mod h1:0aztFxNzVQIRSZ8vUr79uH2bS3jwLebwK6q1sgEub+E=\ndario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=\ndario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=\ngithub.com/Azure/azure-amqp-common-go/v4 v4.2.0 h1:q/jLx1KJ8xeI8XGfkOWMN9XrXzAfVTkyvCxPvHCjd2I=\ngithub.com/Azure/azure-amqp-common-go/v4 v4.2.0/go.mod h1:GD3m/WPPma+621UaU6KNjKEo5Hl09z86viKwQjTpV0Q=\ngithub.com/Azure/azure-event-hubs-go/v3 v3.6.2 h1:7rNj1/iqS/i3mUKokA2n2eMYO72TB7lO7OmpbKoakKY=\ngithub.com/Azure/azure-event-hubs-go/v3 v3.6.2/go.mod h1:n+ocYr9j2JCLYqUqz9eI+lx/TEAtL/g6rZzyTFSuIpc=\ngithub.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=\ngithub.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=\ngithub.com/Azure/go-amqp v1.4.0 h1:Xj3caqi4comOF/L1Uc5iuBxR/pB6KumejC01YQOqOR4=\ngithub.com/Azure/go-amqp v1.4.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=\ngithub.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=\ngithub.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=\ngithub.com/Azure/go-autorest/autorest v0.11.30 h1:iaZ1RGz/ALZtN5eq4Nr1SOFSlf2E4pDI3Tcsl+dZPVE=\ngithub.com/Azure/go-autorest/autorest v0.11.30/go.mod h1:t1kpPIOpIVX7annvothKvb0stsrXa37i7b+xpmBW8Fs=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8=\ngithub.com/Azure/go-autorest/autorest/azure/auth v0.4.2 h1:iM6UAvjR97ZIeR93qTcwpKNMpV+/FTWjwEbuPD495Tk=\ngithub.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM=\ngithub.com/Azure/go-autorest/autorest/azure/cli v0.3.1 h1:LXl088ZQlP0SBppGFsRZonW6hSvwgL5gRByMbvUbx8U=\ngithub.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw=\ngithub.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=\ngithub.com/Azure/go-autorest/autorest/date v0.3.1 h1:o9Z8Jyt+VJJTCZ/UORishuHOusBwolhjokt9s5k8I4w=\ngithub.com/Azure/go-autorest/autorest/date v0.3.1/go.mod h1:Dz/RDmXlfiFFS/eW+b/xMUSFs1tboPVy6UjgADToWDM=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=\ngithub.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc=\ngithub.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M=\ngithub.com/Azure/go-autorest/autorest/validation v0.3.2 h1:myD3tcvs+Fk1bkJ1Xx7xidop4z4FWvWADiMGMXeVd2E=\ngithub.com/Azure/go-autorest/autorest/validation v0.3.2/go.mod h1:4z7eU88lSINAB5XL8mhfPumiUdoAQo/c7qXwbsM8Zhc=\ngithub.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=\ngithub.com/Azure/go-autorest/logger v0.2.2 h1:hYqBsEBywrrOSW24kkOCXRcKfKhK76OzLTfF+MYDE2o=\ngithub.com/Azure/go-autorest/logger v0.2.2/go.mod h1:I5fg9K52o+iuydlWfa9T5K6WFos9XYr9dYTFzpqgibw=\ngithub.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=\ngithub.com/Azure/go-autorest/tracing v0.6.1 h1:YUMSrC/CeD1ZnnXcNYU4a/fzsO35u2Fsful9L/2nyR0=\ngithub.com/Azure/go-autorest/tracing v0.6.1/go.mod h1:/3EgjbsjraOqiicERAeu3m7/z0x1TzjQGAwDrJrXGkc=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=\ngithub.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=\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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ=\ngithub.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=\ngithub.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=\ngithub.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8=\ngithub.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q=\ngithub.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=\ngithub.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=\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/devigned/tab v0.1.1 h1:3mD6Kb1mUOYeLpJvTVSDwSg5ZsfSxfvxGRTxRsJsITA=\ngithub.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=\ngithub.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=\ngithub.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=\ngithub.com/docker/cli v29.0.3+incompatible h1:8J+PZIcF2xLd6h5sHPsp5pvvJA+Sr2wGQxHkRl53a1E=\ngithub.com/docker/cli v29.0.3+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-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/elazarl/goproxy v1.8.0 h1:dt561rX7UAYMeFRLtzFx6uQGl2TpL1dr6uCG23nFQSY=\ngithub.com/elazarl/goproxy v1.8.0/go.mod h1:b5xm6W48AUHNpRTCvlnd0YVh+JafCCtsLsJZvvNTz+E=\ngithub.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=\ngithub.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=\ngithub.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=\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.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=\ngithub.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=\ngithub.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=\ngithub.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=\ngithub.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg=\ngithub.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo=\ngithub.com/fluxcd/helm-controller/api v1.4.5 h1:hMEBtgXUbJjp+ah0jPI3OOQNVngoToOQvTgFgVpAjNg=\ngithub.com/fluxcd/helm-controller/api v1.4.5/go.mod h1:rCgx3qhjjtoIH+1EbzFC2vN71/pp0PgMDrZnGCZX5XY=\ngithub.com/fluxcd/image-automation-controller/api v1.0.4 h1:Fgdy97hXkyh/JFjxLIyq4ZDHsKsa49aumtrvIyjVd08=\ngithub.com/fluxcd/image-automation-controller/api v1.0.4/go.mod h1:LLBf4XQJAgnpIMlZUwfpVIkCdUtBOi31B6fDbPwBCq4=\ngithub.com/fluxcd/image-reflector-controller/api v1.0.4 h1:/JGpTZf4eMcKG2FpWfP5H7SneSrD5P8EvwGnHiH/WLY=\ngithub.com/fluxcd/image-reflector-controller/api v1.0.4/go.mod h1:5GS4ojHaz+W6hK80WakGIOYk8sn93AyV5X+YOne1XMw=\ngithub.com/fluxcd/kustomize-controller/api v1.7.3 h1:g+C9Il+H33DQi/ZiQ8KpTvL9KXebXnS4oM/0uJ/C8Gw=\ngithub.com/fluxcd/kustomize-controller/api v1.7.3/go.mod h1:Yj80JyfQpBUgLhsUZ/c86qcvPGO2+P1VCKsb8fL+L/k=\ngithub.com/fluxcd/notification-controller/api v1.7.5 h1:6CO5bKyjodiK9exQFOdBcz0XLeo17rrrWQBTJL9NNa8=\ngithub.com/fluxcd/notification-controller/api v1.7.5/go.mod h1:IciwSg8Q0pVtdbsyDyEXx/MxBKWeagxAazpm64C8oCE=\ngithub.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA=\ngithub.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4=\ngithub.com/fluxcd/pkg/apis/event v0.25.0 h1:zdwytvDhG+fk+Ywl5DOtv7TklkrVgM21WHm1f+YhleE=\ngithub.com/fluxcd/pkg/apis/event v0.25.0/go.mod h1:TlK8HWYrTwl0raqBRC+ROoNpYW5fdVnwcwOBOx5Kzw8=\ngithub.com/fluxcd/pkg/apis/kustomize v1.16.0 h1:PhWXEhqQqsisIpwp1/wHvTvo+MO+GGzsBPoN0ZnRE3Y=\ngithub.com/fluxcd/pkg/apis/kustomize v1.16.0/go.mod h1:IZOy4CCtR/hxMGb7erK1RfbGnczVv4/dRBoVD37AywI=\ngithub.com/fluxcd/pkg/apis/meta v1.26.0 h1:dxP1FfBpTCYso6odzRcltVnnRuBb2VyhhgV0VX9YbUE=\ngithub.com/fluxcd/pkg/apis/meta v1.26.0/go.mod h1:c7o6mJGLCMvNrfdinGZehkrdZuFT9vZdZNrn66DtVD0=\ngithub.com/fluxcd/pkg/git v0.46.0 h1:QMh0+ZzQ2jO6rIGj4ffR5trZ8g/cxvt8cVajReJ8Iyw=\ngithub.com/fluxcd/pkg/git v0.46.0/go.mod h1:iHcIjx9c8zye3PQiajTJYxgOMRiy7WCs+hfLKDswpfI=\ngithub.com/fluxcd/pkg/gittestserver v0.26.0 h1:+RZrCzFRsE+d5WaqAoqaPCEgcgv/jZp6+f7DS0+Ynb8=\ngithub.com/fluxcd/pkg/gittestserver v0.26.0/go.mod h1:7fybYb0yej1fFNiF1ohs0Jr0XzyaZQ/cRh3AFEoCtuc=\ngithub.com/fluxcd/pkg/runtime v0.103.0 h1:J5y5GPhWdkyqIUBlaI1FP2N02TtZmsjbWhhZubuTSFk=\ngithub.com/fluxcd/pkg/runtime v0.103.0/go.mod h1:mbo2f3azo3yVQgm7XZGxQB6/2zvzQ5Wgtd8TjRRwwAw=\ngithub.com/fluxcd/pkg/ssh v0.24.0 h1:hrPlxs0hhXf32DRqs68VbsXs0XfQMphyRVIk0rYYJa4=\ngithub.com/fluxcd/pkg/ssh v0.24.0/go.mod h1:xWammEqalrpurpcMiixJRXtynRQtBEoqheyU5F/vWrg=\ngithub.com/fluxcd/pkg/version v0.14.0 h1:T3llSc8sUnsuFrW5ng2ePSfXwGXUKv0YG9QXf0ErhWw=\ngithub.com/fluxcd/pkg/version v0.14.0/go.mod h1:YHdg/78kzf+kCqS+SqSOiUxum5AjxlixiqwpX6AUZB8=\ngithub.com/fluxcd/source-controller/api v1.7.4 h1:+EOVnRA9LmLxOx7J273l7IOEU39m+Slt/nQGBy69ygs=\ngithub.com/fluxcd/source-controller/api v1.7.4/go.mod h1:ruf49LEgZRBfcP+eshl2n9SX1MfHayCcViAIGnZcaDY=\ngithub.com/fluxcd/test-infra/tftestenv v0.0.0-20250626232827-e0ca9c3f8d7b h1:FSPtvaVgL8azcyweqLmD71elAw4vozuXH/QvsJQ7tg0=\ngithub.com/fluxcd/test-infra/tftestenv v0.0.0-20250626232827-e0ca9c3f8d7b/go.mod h1:liFlLEXgambGVdWSJ4JzbIHf1Vjpp1HwUyPazPIVZug=\ngithub.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=\ngithub.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=\ngithub.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=\ngithub.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=\ngithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=\ngithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=\ngithub.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=\ngithub.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=\ngithub.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=\ngithub.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=\ngithub.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=\ngithub.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=\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-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=\ngithub.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=\ngithub.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=\ngithub.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=\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-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/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=\ngithub.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\ngithub.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=\ngithub.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=\ngithub.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\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/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=\ngithub.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I=\ngithub.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=\ngithub.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=\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.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.2/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/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-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\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-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=\ngithub.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=\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/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24=\ngithub.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I=\ngithub.com/hashicorp/terraform-exec v0.24.0 h1:mL0xlk9H5g2bn0pPF6JQZk5YlByqSqrO5VoaNtAf8OE=\ngithub.com/hashicorp/terraform-exec v0.24.0/go.mod h1:lluc/rDYfAhYdslLJQg3J0oDqo88oGQAdHR+wDqFvo4=\ngithub.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU=\ngithub.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE=\ngithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=\ngithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=\ngithub.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=\ngithub.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=\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/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\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/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=\ngithub.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=\ngithub.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=\ngithub.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\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/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-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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 h1:YH424zrwLTlyHSH/GzLMJeu5zhYVZSx5RQxGKm1h96s=\ngithub.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5/go.mod h1:PoGiBqKSQK1vIfQ+yVaFcGjDySHvym6FM1cNYnwzbrY=\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/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=\ngithub.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=\ngithub.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=\ngithub.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=\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/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=\ngithub.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=\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/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.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=\ngithub.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=\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/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=\ngithub.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=\ngithub.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=\ngithub.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=\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/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=\ngithub.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/zclconf/go-cty v1.16.4 h1:QGXaag7/7dCzb+odlGrgr+YmYZFaOCMW6DEpS+UD1eE=\ngithub.com/zclconf/go-cty v1.16.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=\ngo.einride.tech/aip v0.73.0 h1:bPo4oqBo2ZQeBKo4ZzLb1kxYXTY1ysJhpvQyfuGzvps=\ngo.einride.tech/aip v0.73.0/go.mod h1:Mj7rFbmXEgw0dq1dqJ7JGMvYCZZVxmGOR3S4ZcV5LvQ=\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.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=\ngo.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=\ngo.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=\ngo.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=\ngo.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=\ngo.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=\ngo.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=\ngo.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=\ngo.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=\ngolang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=\ngolang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=\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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=\ngolang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=\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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=\ngolang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=\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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/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-20190412213103-97732733099d/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=\ngolang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=\ngolang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=\ngolang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=\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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=\ngolang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=\ngolang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\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.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=\ngoogle.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM=\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-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/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.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=\ngoogle.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=\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-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/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=\ngopkg.in/evanphx/json-patch.v4 v4.13.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/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=\ngopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\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.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=\ngotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=\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.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=\nk8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=\nk8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpApE29c0=\nk8s.io/apiextensions-apiserver v0.35.2/go.mod h1:OdyGvcO1FtMDWQ+rRh/Ei3b6X3g2+ZDHd0MSRGeS8rU=\nk8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=\nk8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=\nk8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=\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-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=\nsigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\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/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "tests/integration/image_repo_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/gomega\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tautomationv1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\treflectorv1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nfunc TestImageRepositoryAndAutomation(t *testing.T) {\n\tg := NewWithT(t)\n\tctx := context.TODO()\n\tbranchName := \"image-repository\"\n\ttestID := branchName + \"-\" + randStringRunes(5)\n\timageURL := fmt.Sprintf(\"%s/podinfo\", cfg.testRegistry)\n\n\tmanifest := fmt.Sprintf(`apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: podinfo\n  namespace: %[1]s\nspec:\n  selector:\n    matchLabels:\n      app: podinfo\n  template:\n    metadata:\n      labels:\n        app: podinfo\n    spec:\n      containers:\n      - name: podinfod\n        image: %[2]s:%[3]s # {\"$imagepolicy\": \"%[1]s:podinfo\"}\n        readinessProbe:\n          exec:\n            command:\n            - podcli\n            - check\n            - http\n            - localhost:9898/readyz\n          initialDelaySeconds: 5\n          timeoutSeconds: 5\n`, testID, imageURL, oldPodinfoVersion)\n\n\trepoUrl := getTransportURL(cfg.applicationRepository)\n\tclient, err := getRepository(ctx, t.TempDir(), repoUrl, defaultBranch, cfg.defaultAuthOpts)\n\tg.Expect(err).ToNot(HaveOccurred())\n\tfiles := make(map[string]io.Reader)\n\tfiles[testID+\"/podinfo.yaml\"] = strings.NewReader(manifest)\n\n\terr = commitAndPushAll(ctx, client, files, branchName)\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\terr = setUpFluxConfig(ctx, testID, nsConfig{\n\t\trepoURL: repoUrl,\n\t\tpath:    testID,\n\t\tref: &sourcev1.GitRepositoryRef{\n\t\t\tBranch: branchName,\n\t\t},\n\t})\n\tg.Expect(err).ToNot(HaveOccurred())\n\tt.Cleanup(func() {\n\t\terr := tearDownFluxConfig(ctx, testID)\n\t\tif err != nil {\n\t\t\tt.Logf(\"failed to delete resources in '%s' namespace: %s\", testID, err)\n\t\t}\n\t})\n\tt.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })\n\n\tg.Eventually(func() bool {\n\t\terr := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}, testTimeout, testInterval).Should(BeTrue())\n\n\timageRepository := reflectorv1.ImageRepository{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"podinfo\",\n\t\t\tNamespace: testID,\n\t\t},\n\t\tSpec: reflectorv1.ImageRepositorySpec{\n\t\t\tImage: imageURL,\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: 1 * time.Minute,\n\t\t\t},\n\t\t\tProvider: infraOpts.Provider,\n\t\t},\n\t}\n\tg.Expect(testEnv.Create(ctx, &imageRepository)).To(Succeed())\n\tdefer testEnv.Delete(ctx, &imageRepository)\n\n\timagePolicy := reflectorv1.ImagePolicy{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"podinfo\",\n\t\t\tNamespace: testID,\n\t\t},\n\t\tSpec: reflectorv1.ImagePolicySpec{\n\t\t\tImageRepositoryRef: meta.NamespacedObjectReference{\n\t\t\t\tName: imageRepository.Name,\n\t\t\t},\n\t\t\tPolicy: reflectorv1.ImagePolicyChoice{\n\t\t\t\tSemVer: &reflectorv1.SemVerPolicy{\n\t\t\t\t\tRange: \"6.0.x\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tg.Expect(testEnv.Create(ctx, &imagePolicy)).To(Succeed())\n\tdefer testEnv.Delete(ctx, &imagePolicy)\n\n\timageAutomation := automationv1.ImageUpdateAutomation{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"podinfo\",\n\t\t\tNamespace: testID,\n\t\t},\n\t\tSpec: automationv1.ImageUpdateAutomationSpec{\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: 1 * time.Minute,\n\t\t\t},\n\t\t\tSourceRef: automationv1.CrossNamespaceSourceReference{\n\t\t\t\tKind: \"GitRepository\",\n\t\t\t\tName: testID,\n\t\t\t},\n\t\t\tGitSpec: &automationv1.GitSpec{\n\t\t\t\tCheckout: &automationv1.GitCheckoutSpec{\n\t\t\t\t\tReference: sourcev1.GitRepositoryRef{\n\t\t\t\t\t\tBranch: branchName,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tCommit: automationv1.CommitSpec{\n\t\t\t\t\tAuthor: automationv1.CommitUser{\n\t\t\t\t\t\tEmail: \"imageautomation@example.com\",\n\t\t\t\t\t\tName:  \"imageautomation\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tUpdate: &automationv1.UpdateStrategy{\n\t\t\t\tPath:     testID,\n\t\t\t\tStrategy: automationv1.UpdateStrategySetters,\n\t\t\t},\n\t\t},\n\t}\n\tg.Expect(testEnv.Create(ctx, &imageAutomation)).To(Succeed())\n\tdefer testEnv.Delete(ctx, &imageAutomation)\n\n\t// Wait for image repository to be ready\n\tg.Eventually(func() bool {\n\t\tclient, err := getRepository(ctx, t.TempDir(), repoUrl, branchName, cfg.defaultAuthOpts)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tb, err := os.ReadFile(filepath.Join(client.Path(), testID, \"podinfo.yaml\"))\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tif bytes.Contains(b, []byte(newPodinfoVersion)) == false {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}, testTimeout, testInterval).Should(BeTrue())\n}\n"
  },
  {
    "path": "tests/integration/notification_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/gomega\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tnotiv1 \"github.com/fluxcd/notification-controller/api/v1\"\n\tnotiv1beta3 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n\tevents \"github.com/fluxcd/pkg/apis/event/v1beta1\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nfunc TestNotification(t *testing.T) {\n\tg := NewWithT(t)\n\n\tctx := context.TODO()\n\tbranchName := \"test-notification\"\n\ttestID := branchName + \"-\" + randStringRunes(5)\n\tdefer cfg.notificationCfg.closeChan()\n\n\t// Setup Flux resources\n\tmanifest := `apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: foobar`\n\trepoUrl := getTransportURL(cfg.applicationRepository)\n\tclient, err := getRepository(ctx, t.TempDir(), repoUrl, defaultBranch, cfg.defaultAuthOpts)\n\tg.Expect(err).ToNot(HaveOccurred())\n\tfiles := make(map[string]io.Reader)\n\tfiles[\"configmap.yaml\"] = strings.NewReader(manifest)\n\terr = commitAndPushAll(ctx, client, files, branchName)\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\tnamespace := corev1.Namespace{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: testID,\n\t\t},\n\t}\n\tg.Expect(testEnv.Create(ctx, &namespace)).To(Succeed())\n\tdefer testEnv.Delete(ctx, &namespace)\n\n\tprovider := notiv1beta3.Provider{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      testID,\n\t\t\tNamespace: testID,\n\t\t},\n\t\tSpec: notiv1beta3.ProviderSpec{\n\t\t\tType:    cfg.notificationCfg.providerType,\n\t\t\tAddress: cfg.notificationCfg.providerAddress,\n\t\t\tChannel: cfg.notificationCfg.providerChannel,\n\t\t},\n\t}\n\n\tif cfg.notificationCfg.secret != nil {\n\t\tsecret := corev1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      testID,\n\t\t\t\tNamespace: testID,\n\t\t\t},\n\t\t\tStringData: cfg.notificationCfg.secret,\n\t\t}\n\n\t\tg.Expect(testEnv.Create(ctx, &secret)).To(Succeed())\n\t\tdefer testEnv.Delete(ctx, &secret)\n\n\t\tprovider.Spec.SecretRef = &meta.LocalObjectReference{\n\t\t\tName: testID,\n\t\t}\n\t}\n\n\tg.Expect(testEnv.Create(ctx, &provider)).To(Succeed())\n\tdefer testEnv.Delete(ctx, &provider)\n\n\talert := notiv1beta3.Alert{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      testID,\n\t\t\tNamespace: testID,\n\t\t},\n\t\tSpec: notiv1beta3.AlertSpec{\n\t\t\tProviderRef: meta.LocalObjectReference{\n\t\t\t\tName: provider.Name,\n\t\t\t},\n\t\t\tEventSources: []notiv1.CrossNamespaceObjectReference{\n\t\t\t\t{\n\t\t\t\t\tKind:      \"Kustomization\",\n\t\t\t\t\tName:      testID,\n\t\t\t\t\tNamespace: testID,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tg.Expect(testEnv.Create(ctx, &alert)).ToNot(HaveOccurred())\n\tdefer testEnv.Delete(ctx, &alert)\n\n\tmodifyKsSpec := func(spec *kustomizev1.KustomizationSpec) {\n\t\tspec.Interval = metav1.Duration{Duration: 30 * time.Second}\n\t\tspec.HealthChecks = []meta.NamespacedObjectKindReference{\n\t\t\t{\n\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\tKind:       \"ConfigMap\",\n\t\t\t\tName:       \"foobar\",\n\t\t\t\tNamespace:  testID,\n\t\t\t},\n\t\t}\n\t}\n\tg.Expect(setUpFluxConfig(ctx, testID, nsConfig{\n\t\trepoURL: repoUrl,\n\t\tref: &sourcev1.GitRepositoryRef{\n\t\t\tBranch: branchName,\n\t\t},\n\t\tpath:         \"./\",\n\t\tmodifyKsSpec: modifyKsSpec,\n\t})).To(Succeed())\n\tt.Cleanup(func() {\n\t\terr := tearDownFluxConfig(ctx, testID)\n\t\tif err != nil {\n\t\t\tt.Logf(\"failed to delete resources in '%s' namespace: %s\", testID, err)\n\t\t}\n\t})\n\tt.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })\n\n\tg.Eventually(func() bool {\n\t\terr := verifyGitAndKustomization(ctx, testEnv, testID, testID)\n\t\tif err != nil {\n\t\t\tt.Log(err)\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}, testTimeout, testInterval).Should(BeTrue())\n\n\t// Wait to read event from notification channel.\n\tg.Eventually(func() bool {\n\t\tselect {\n\t\tcase eventJson := <-cfg.notificationCfg.notificationChan:\n\t\t\tevent := &events.Event{}\n\t\t\terr := json.Unmarshal([]byte(eventJson), event)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"the received event type does not match Flux format, error: %v\", err)\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif event.InvolvedObject.Kind == kustomizev1.KustomizationKind &&\n\t\t\t\tevent.InvolvedObject.Name == testID && event.InvolvedObject.Namespace == testID {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\treturn false\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}, testTimeout, 1*time.Second).Should(BeTrue())\n}\n"
  },
  {
    "path": "tests/integration/oci_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/gomega\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n)\n\nfunc TestOCIHelmRelease(t *testing.T) {\n\tg := NewWithT(t)\n\tctx := context.TODO()\n\n\t// Create namespace for test\n\ttestID := \"oci-helm-\" + randStringRunes(5)\n\tnamespace := corev1.Namespace{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: testID,\n\t\t},\n\t}\n\tg.Expect(testEnv.Create(ctx, &namespace)).To(Succeed())\n\tdefer testEnv.Delete(ctx, &namespace)\n\tt.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })\n\n\trepoURL := fmt.Sprintf(\"%s/charts/podinfo\", cfg.testRegistry)\n\terr := pushImagesFromURL(repoURL, \"ghcr.io/stefanprodan/charts/podinfo:6.2.0\", []string{\"6.2.0\"})\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\t// Create HelmRepository.\n\thelmRepository := sourcev1.HelmRepository{\n\t\tObjectMeta: metav1.ObjectMeta{Name: testID, Namespace: testID},\n\t\tSpec: sourcev1.HelmRepositorySpec{\n\t\t\tURL: fmt.Sprintf(\"oci://%s\", cfg.testRegistry),\n\t\t\tInterval: metav1.Duration{\n\t\t\t\tDuration: 5 * time.Minute,\n\t\t\t},\n\t\t\tProvider:        infraOpts.Provider,\n\t\t\tPassCredentials: true,\n\t\t\tType:            \"oci\",\n\t\t},\n\t}\n\n\tg.Expect(testEnv.Create(ctx, &helmRepository)).To(Succeed())\n\tdefer testEnv.Delete(ctx, &helmRepository)\n\n\t// create helm release\n\thelmRelease := helmv2.HelmRelease{\n\t\tObjectMeta: metav1.ObjectMeta{Name: testID, Namespace: testID},\n\t\tSpec: helmv2.HelmReleaseSpec{\n\t\t\tChart: &helmv2.HelmChartTemplate{\n\t\t\t\tSpec: helmv2.HelmChartTemplateSpec{\n\t\t\t\t\tInterval: &metav1.Duration{\n\t\t\t\t\t\tDuration: 10 * time.Minute,\n\t\t\t\t\t},\n\t\t\t\t\tChart:   \"charts/podinfo\",\n\t\t\t\t\tVersion: \"6.2.0\",\n\t\t\t\t\tSourceRef: helmv2.CrossNamespaceObjectReference{\n\t\t\t\t\t\tKind:      sourcev1.HelmRepositoryKind,\n\t\t\t\t\t\tName:      helmRepository.Name,\n\t\t\t\t\t\tNamespace: helmRepository.Namespace,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tg.Expect(testEnv.Create(ctx, &helmRelease)).To(Succeed())\n\tdefer testEnv.Delete(ctx, &helmRelease)\n\n\tg.Eventually(func() bool {\n\t\tchart := &sourcev1.HelmChart{}\n\t\tnn := types.NamespacedName{\n\t\t\tName:      fmt.Sprintf(\"%s-%s\", helmRelease.Name, helmRelease.Namespace),\n\t\t\tNamespace: helmRelease.Namespace,\n\t\t}\n\t\tif err := testEnv.Get(ctx, nn, chart); err != nil {\n\t\t\tt.Logf(\"error getting helm chart: %s\", err.Error())\n\t\t\treturn false\n\t\t}\n\t\tif err := checkReadyCondition(chart); err != nil {\n\t\t\tt.Logf(\"HelmChart not ready: %s\", err)\n\t\t\treturn false\n\t\t}\n\n\t\tobj := &helmv2.HelmRelease{}\n\t\tnn = types.NamespacedName{Name: helmRelease.Name, Namespace: helmRelease.Namespace}\n\t\tif err := testEnv.Get(ctx, nn, obj); err != nil {\n\t\t\tt.Logf(\"error getting helm release: %s\", err.Error())\n\t\t\treturn false\n\t\t}\n\n\t\tif err := checkReadyCondition(obj); err != nil {\n\t\t\t// Log all HelmRelease conditions for full picture.\n\t\t\tvar condSummary []string\n\t\t\tfor _, c := range obj.Status.Conditions {\n\t\t\t\tcondSummary = append(condSummary, fmt.Sprintf(\"%s=%s (%s)\", c.Type, c.Status, c.Message))\n\t\t\t}\n\t\t\tt.Logf(\"HelmRelease not ready: conditions=[%s]\", strings.Join(condSummary, \"; \"))\n\n\t\t\t// Log pod states in the release namespace.\n\t\t\tlogNamespacePods(t, ctx, testID)\n\t\t\treturn false\n\t\t}\n\n\t\treturn true\n\t}, testTimeout, testInterval).Should(BeTrue())\n}\n"
  },
  {
    "path": "tests/integration/sops_encryption_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"testing\"\n\n\t. \"github.com/onsi/gomega\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\t\"github.com/fluxcd/test-infra/tftestenv\"\n)\n\nfunc TestKeyVaultSops(t *testing.T) {\n\tg := NewWithT(t)\n\tctx := context.TODO()\n\tbranchName := \"key-vault\"\n\ttestID := branchName + \"-\" + randStringRunes(5)\n\tsecretYaml := `apiVersion: v1\nkind: Secret\nmetadata:\n  name: \"test\"\nstringData:\n  foo: \"bar\"`\n\n\trepoUrl := getTransportURL(cfg.applicationRepository)\n\ttmpDir := t.TempDir()\n\tclient, err := getRepository(ctx, tmpDir, repoUrl, defaultBranch, cfg.defaultAuthOpts)\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\tdir := client.Path() + \"/key-vault-sops\"\n\tg.Expect(os.Mkdir(dir, 0o700)).To(Succeed())\n\n\tfilename := dir + \"secret.enc.yaml\"\n\tf, err := os.Create(filename)\n\tg.Expect(err).ToNot(HaveOccurred())\n\tdefer f.Close()\n\n\t_, err = f.Write([]byte(secretYaml))\n\tg.Expect(err).ToNot(HaveOccurred())\n\tg.Expect(f.Sync()).To(Succeed())\n\n\terr = tftestenv.RunCommand(ctx, client.Path(),\n\t\tfmt.Sprintf(\"sops --encrypt --encrypted-regex '^(data|stringData)$' %s --in-place %s\", cfg.sopsArgs, filename),\n\t\ttftestenv.RunCommandOptions{})\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\tr, err := os.Open(filename)\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\tfiles := make(map[string]io.Reader)\n\tfiles[\"key-vault-sops/secret.enc.yaml\"] = r\n\terr = commitAndPushAll(ctx, client, files, branchName)\n\tg.Expect(err).ToNot(HaveOccurred())\n\n\tmodifyKsSpec := func(spec *kustomizev1.KustomizationSpec) {\n\t\tspec.Decryption = &kustomizev1.Decryption{\n\t\t\tProvider: \"sops\",\n\t\t}\n\t\tif cfg.sopsSecretData != nil {\n\t\t\tspec.Decryption.SecretRef = &meta.LocalObjectReference{\n\t\t\t\tName: \"sops-keys\",\n\t\t\t}\n\t\t}\n\t}\n\n\terr = setUpFluxConfig(ctx, testID, nsConfig{\n\t\tref: &sourcev1.GitRepositoryRef{\n\t\t\tBranch: branchName,\n\t\t},\n\t\trepoURL:      repoUrl,\n\t\tpath:         \"./key-vault-sops\",\n\t\tmodifyKsSpec: modifyKsSpec,\n\t\tprotocol:     cfg.defaultGitTransport,\n\t})\n\tg.Expect(err).ToNot(HaveOccurred())\n\tt.Cleanup(func() {\n\t\terr := tearDownFluxConfig(ctx, testID)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"failed to delete resources in '%s' namespace\", testID)\n\t\t}\n\t})\n\tt.Cleanup(func() { dumpDiagnostics(t, ctx, testID) })\n\n\tif cfg.sopsSecretData != nil {\n\t\tsecret := corev1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"sops-keys\",\n\t\t\t\tNamespace: testID,\n\t\t\t},\n\t\t\tStringData: cfg.sopsSecretData,\n\t\t}\n\t\tg.Expect(testEnv.Create(ctx, &secret)).To(Succeed())\n\t\tdefer testEnv.Delete(ctx, &secret)\n\t}\n\n\tg.Eventually(func() bool {\n\t\terr := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tnn := types.NamespacedName{Name: \"test\", Namespace: testID}\n\t\tsecret := &corev1.Secret{}\n\t\terr = testEnv.Get(ctx, nn, secret)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tif string(secret.Data[\"foo\"]) == \"bar\" {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t}, testTimeout, testInterval).Should(BeTrue())\n}\n"
  },
  {
    "path": "tests/integration/suite_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/hashicorp/terraform-exec/tfexec\"\n\ttfjson \"github.com/hashicorp/terraform-json\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\tautomationv1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\treflectorv1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\tnotiv1beta3 \"github.com/fluxcd/notification-controller/api/v1beta3\"\n\t\"github.com/fluxcd/pkg/git\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\t\"github.com/fluxcd/test-infra/tftestenv\"\n)\n\nconst (\n\t// azureTerraformPath is the path to the folder containing the\n\t// terraform files for azure infra\n\tazureTerraformPath = \"./terraform/azure\"\n\t// gcpTerraformPath is the path to the folder containing the\n\t// terraform files for gcp infra\n\tgcpTerraformPath = \"./terraform/gcp\"\n\n\t// kubeconfigPath is the path of the file containing the kubeconfig\n\tkubeconfigPath = \"./build/kubeconfig\"\n\n\t// fluxBin is the path to the flux binary.\n\tfluxBin = \"./build/flux\"\n\n\t// default branch to be used when cloning git repositories\n\tdefaultBranch = \"main\"\n\n\t// envVarGitRepoSSHPath is the environment variable that contains the path\n\t// to the ssh key for the git repository\n\tenvVarGitRepoSSHPath = \"GITREPO_SSH_PATH\"\n\t// envVarGitRepoSSHPubPath is the environment variable that contains the path\n\t// to the ssh public key for the git repository\n\tenvVarGitRepoSSHPubPath = \"GITREPO_SSH_PUB_PATH\"\n)\n\nvar (\n\t// supportedProviders are the providers supported by the test.\n\tsupportedProviders = []string{\"azure\", \"gcp\"}\n\n\t// cfg is a struct containing different variables needed for the test.\n\tcfg *testConfig\n\n\t// infraOpts are the options for running the terraform environment\n\tinfraOpts tftestenv.Options\n\n\t// versions to tag and push for the podinfo image\n\toldPodinfoVersion = \"6.0.0\"\n\tnewPodinfoVersion = \"6.0.1\"\n\tpodinfoTags       = []string{oldPodinfoVersion, newPodinfoVersion}\n\n\t// testEnv is the test environment. It contains test infrastructure and\n\t// kubernetes client of the created cluster.\n\ttestEnv *tftestenv.Environment\n\n\t// testTimeout is used as a timeout when testing a condition with gomega's eventually\n\ttestTimeout = 60 * time.Second\n\t// testInterval is used as an interval when testing a condition with gomega's eventually\n\ttestInterval = 5 * time.Second\n\n\trandom *rand.Rand\n\n\tletterRunes = []rune(\"abcdefghijklmnopqrstuvwxyz1234567890\")\n\n\tlocalImg = \"ghcr.io/stefanprodan/podinfo\"\n)\n\n// testConfig hold different variable that will be needed by the different test functions.\ntype testConfig struct {\n\t// authentication info for git repositories\n\tgitPat                string\n\tgitUsername           string\n\tgitPrivateKey         string\n\tgitPublicKey          string\n\tdefaultGitTransport   git.TransportType\n\tdefaultAuthOpts       *git.AuthOptions\n\tknownHosts            string\n\tfleetInfraRepository  gitUrl\n\tapplicationRepository gitUrl\n\n\t// sopsArgs is the cloud provider dependent argument to pass to the sops cli\n\tsopsArgs string\n\n\t// notificationCfg contains the values needed to properly set up notification on the\n\t// cluster.\n\tnotificationCfg notificationConfig\n\n\t// sopsSecretData is the secret's data for the sops decryption\n\tsopsSecretData map[string]string\n\t// kustomizationYaml is the  content of the kustomization.yaml for customizing the Flux manifests\n\tkustomizationYaml string\n\n\t// testRegistry is the registry of the cloud provider.\n\ttestRegistry string\n}\n\n// notificationConfig contains various fields for configuring\n// providers and testing notifications for the different\n// cloud providers.\ntype notificationConfig struct {\n\tproviderChannel  string\n\tproviderType     string\n\tproviderAddress  string\n\tsecret           map[string]string\n\tnotificationChan chan []byte\n\tcloseChan        func()\n}\n\n// gitUrl contains the http/ssh urls for the created git repositories\n// on the various cloud providers.\ntype gitUrl struct {\n\thttp string\n\tssh  string\n}\n\n// getTestConfig gets the test configuration that contains different variables for running the tests\ntype getTestConfig func(ctx context.Context, output map[string]*tfjson.StateOutput) (*testConfig, error)\n\n// registryLoginFunc is used to perform registry login against a provider based\n// on the terraform state output values. It returns the test registry\n// to test against, read from the terraform state output.\ntype registryLoginFunc func(ctx context.Context, output map[string]*tfjson.StateOutput) (string, error)\n\n// providerConfig contains the test configurations for the different cloud providers\ntype providerConfig struct {\n\tterraformPath    string\n\tcreateKubeconfig tftestenv.CreateKubeconfig\n\tgetTestConfig    getTestConfig\n\t// registryLogin is used to perform registry login.\n\tregistryLogin registryLoginFunc\n}\n\nfunc init() {\n\tutilruntime.Must(sourcev1.AddToScheme(scheme.Scheme))\n\tutilruntime.Must(kustomizev1.AddToScheme(scheme.Scheme))\n\tutilruntime.Must(helmv2.AddToScheme(scheme.Scheme))\n\tutilruntime.Must(reflectorv1.AddToScheme(scheme.Scheme))\n\tutilruntime.Must(automationv1.AddToScheme(scheme.Scheme))\n\tutilruntime.Must(notiv1beta3.AddToScheme(scheme.Scheme))\n\n\trandom = rand.New(rand.NewSource(time.Now().UnixNano()))\n}\n\nfunc TestMain(m *testing.M) {\n\tctx := context.TODO()\n\n\tinfraOpts.Bindflags(flag.CommandLine)\n\tflag.Parse()\n\n\t// Validate the provider.\n\tif infraOpts.Provider == \"\" {\n\t\tlog.Fatalf(\"-provider flag must be set to one of %v\", supportedProviders)\n\t}\n\tvar supported bool\n\tfor _, p := range supportedProviders {\n\t\tif p == infraOpts.Provider {\n\t\t\tsupported = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !supported {\n\t\tlog.Fatalf(\"Unsupported provider %q, must be one of %v\", infraOpts.Provider, supportedProviders)\n\t}\n\t// get provider specific configuration\n\tproviderCfg := getProviderConfig(infraOpts.Provider)\n\tif providerCfg == nil {\n\t\tlog.Fatalf(\"Failed to get provider config for %q\", infraOpts.Provider)\n\t}\n\n\t// Run destroy-only mode if enabled.\n\tif infraOpts.DestroyOnly {\n\t\tlog.Println(\"Running in destroy-only mode...\")\n\t\tenvOpts := []tftestenv.EnvironmentOption{\n\t\t\ttftestenv.WithVerbose(infraOpts.Verbose),\n\t\t\t// Ignore any state lock in destroy-only mode.\n\t\t\ttftestenv.WithTfDestroyOptions(tfexec.Lock(false)),\n\t\t}\n\t\tif err := tftestenv.Destroy(ctx, providerCfg.terraformPath, envOpts...); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tos.Exit(0)\n\t}\n\n\t// Initialize with non-zero exit code to indicate failure by default unless\n\t// set by a successful test run.\n\texitCode := 1\n\n\t// Setup Terraform binary and init state\n\tlog.Printf(\"Setting up %s e2e test infrastructure\", infraOpts.Provider)\n\tenvOpts := []tftestenv.EnvironmentOption{\n\t\ttftestenv.WithExisting(infraOpts.Existing),\n\t\ttftestenv.WithRetain(infraOpts.Retain),\n\t\ttftestenv.WithVerbose(infraOpts.Verbose),\n\t\ttftestenv.WithCreateKubeconfig(providerCfg.createKubeconfig),\n\t}\n\n\t// Create terraform infrastructure\n\tvar err error\n\ttestEnv, err = tftestenv.New(ctx, scheme.Scheme, providerCfg.terraformPath, kubeconfigPath, envOpts...)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to provision the test infrastructure: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := testEnv.Stop(ctx); err != nil {\n\t\t\tlog.Printf(\"Failed to stop environment: %v\", err)\n\t\t\texitCode = 1\n\t\t}\n\n\t\t// Log the panic error before exit to surface the cause of panic.\n\t\tif err := recover(); err != nil {\n\t\t\tlog.Printf(\"panic: %v\", err)\n\t\t}\n\t\tos.Exit(exitCode)\n\t}()\n\n\t// get terrraform infrastructure\n\toutputs, err := testEnv.StateOutput(ctx)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Failed to get the terraform state output: %v\", err))\n\t}\n\n\t// get provider specific test configuration\n\tcfg, err = providerCfg.getTestConfig(ctx, outputs)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Failed to get test config: %v\", err))\n\t}\n\n\tregUrl, err := providerCfg.registryLogin(ctx, outputs)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Failed to log into registry: %v\", err))\n\t}\n\n\tcfg.testRegistry = regUrl\n\terr = pushTestImages(ctx, cfg.testRegistry, podinfoTags)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Failed to push test images: %v\", err))\n\t}\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"*-flux-test\")\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Failed to create tmp dir: %v\", err))\n\t}\n\tdefer func() {\n\t\terr := os.RemoveAll(tmpDir)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"error removing tmp dir: %s\\n\", err)\n\t\t}\n\t}()\n\n\tlog.Println(\"Installing flux\")\n\terr = installFlux(ctx, tmpDir, kubeconfigPath)\n\tdefer func() {\n\t\tlog.Println(\"Uninstalling Flux\")\n\t\tif err := uninstallFlux(ctx); err != nil {\n\t\t\tlog.Printf(\"Failed to uninstall: %v\", err)\n\t\t}\n\t}()\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"error installing Flux: %v\", err))\n\t}\n\n\t// On check failure, log and continue. Controllers may be ready by the time\n\t// tests run.\n\tlog.Println(\"Running flux check\")\n\tif err := runFluxCheck(ctx); err != nil {\n\t\tlog.Printf(\"flux check failed: %v\\n\", err)\n\t}\n\n\tlog.Println(\"Running e2e tests\")\n\texitCode = m.Run()\n}\n\nfunc getProviderConfig(provider string) *providerConfig {\n\tswitch provider {\n\tcase \"azure\":\n\t\treturn &providerConfig{\n\t\t\tterraformPath:    azureTerraformPath,\n\t\t\tcreateKubeconfig: createKubeConfigAKS,\n\t\t\tgetTestConfig:    getTestConfigAKS,\n\t\t\tregistryLogin:    registryLoginACR,\n\t\t}\n\tcase \"gcp\":\n\t\treturn &providerConfig{\n\t\t\tterraformPath:    gcpTerraformPath,\n\t\t\tcreateKubeconfig: createKubeConfigGKE,\n\t\t\tgetTestConfig:    getTestConfigGKE,\n\t\t\tregistryLogin:    registryLoginGCR,\n\t\t}\n\t}\n\treturn nil\n}\n\n// pushTestImages pushes the local podinfo image to the remote repository specified\n// by repoURL. The image should be existing on the machine.\nfunc pushTestImages(ctx context.Context, repoURL string, tags []string) error {\n\tfor _, tag := range tags {\n\t\tremoteImg := fmt.Sprintf(\"%s/podinfo:%s\", repoURL, tag)\n\t\terr := tftestenv.RetagAndPush(ctx, fmt.Sprintf(\"%s:%s\", localImg, tag), remoteImg)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t}\n\treturn nil\n}\n\nfunc randStringRunes(n int) string {\n\tb := make([]rune, n)\n\tfor i := range b {\n\t\tb[i] = letterRunes[random.Intn(len(letterRunes))]\n\t}\n\treturn string(b)\n}\n"
  },
  {
    "path": "tests/integration/terraform/azure/aks.tf",
    "content": "module \"aks\" {\n  source = \"git::https://github.com/fluxcd/test-infra.git//tf-modules/azure/aks\"\n\n  name     = local.name\n  location = var.azure_location\n  tags     = var.tags\n}\n\nmodule \"acr\" {\n  source = \"git::https://github.com/fluxcd/test-infra.git//tf-modules/azure/acr\"\n\n  name             = local.name\n  location         = var.azure_location\n  aks_principal_id = [module.aks.principal_id]\n  resource_group   = module.aks.resource_group\n  admin_enabled    = true\n  tags             = var.tags\n\n  depends_on = [module.aks]\n}\n"
  },
  {
    "path": "tests/integration/terraform/azure/azuredevops.tf",
    "content": "resource \"azuredevops_project\" \"e2e\" {\n  name               = local.name\n  visibility         = \"private\"\n  version_control    = \"Git\"\n  work_item_template = \"Agile\"\n  description        = \"Test Project for Flux E2E test - Managed by Terraform\"\n}\n\n\nresource \"azuredevops_git_repository\" \"fleet_infra\" {\n  project_id     = azuredevops_project.e2e.id\n  name           = \"fleet-infra-${local.name}\"\n  default_branch = \"refs/heads/main\"\n  initialization {\n    init_type = \"Clean\"\n  }\n}\n\nresource \"azuredevops_git_repository\" \"application\" {\n  project_id     = azuredevops_project.e2e.id\n  name           = \"application-${local.name}\"\n  default_branch = \"refs/heads/main\"\n  initialization {\n    init_type = \"Clean\"\n  }\n}\n"
  },
  {
    "path": "tests/integration/terraform/azure/event-hub.tf",
    "content": "resource \"azurerm_eventhub_namespace\" \"this\" {\n  name                = local.name\n  location            = var.azure_location\n  resource_group_name = module.aks.resource_group\n  sku                 = \"Basic\"\n  capacity            = 1\n  tags                = var.tags\n}\n\n\nresource \"azurerm_eventhub\" \"this\" {\n  name                = local.name\n  namespace_name      = azurerm_eventhub_namespace.this.name\n  resource_group_name = module.aks.resource_group\n  partition_count     = 1\n  message_retention   = 1\n}\n\nresource \"azurerm_eventhub_authorization_rule\" \"this\" {\n  name                = local.name\n  resource_group_name = module.aks.resource_group\n  namespace_name      = azurerm_eventhub_namespace.this.name\n  eventhub_name       = azurerm_eventhub.this.name\n  listen              = true\n  send                = true\n  manage              = false\n}\n"
  },
  {
    "path": "tests/integration/terraform/azure/keyvault.tf",
    "content": "resource \"azurerm_key_vault\" \"this\" {\n  name                = local.name\n  resource_group_name = module.aks.resource_group\n  location            = var.azure_location\n  tenant_id           = data.azurerm_client_config.current.tenant_id\n  sku_name            = \"standard\"\n  tags                = var.tags\n}\n\nresource \"azurerm_key_vault_access_policy\" \"admin\" {\n  key_vault_id = azurerm_key_vault.this.id\n  tenant_id    = data.azurerm_client_config.current.tenant_id\n  object_id    = data.azurerm_client_config.current.object_id\n\n  key_permissions = [\n    \"Create\",\n    \"Update\",\n    \"Encrypt\",\n    \"Delete\",\n    \"Get\",\n    \"List\",\n    \"Purge\",\n    \"Recover\",\n    \"GetRotationPolicy\",\n    \"SetRotationPolicy\"\n  ]\n\n  secret_permissions = [\n    \"Get\",\n    \"Delete\",\n    \"Purge\",\n    \"Recover\"\n  ]\n\n}\n\nresource \"azurerm_key_vault_access_policy\" \"cluster_binding\" {\n  key_vault_id = azurerm_key_vault.this.id\n  tenant_id    = data.azurerm_client_config.current.tenant_id\n  object_id    = module.aks.principal_id\n\n  key_permissions = [\n    \"Decrypt\",\n    \"Encrypt\",\n  ]\n}\n\nresource \"azurerm_key_vault_key\" \"sops\" {\n  depends_on = [azurerm_key_vault_access_policy.admin]\n\n  name         = \"sops\"\n  key_vault_id = azurerm_key_vault.this.id\n  key_type     = \"RSA\"\n  key_size     = 2048\n  tags         = var.tags\n\n  key_opts = [\n    \"decrypt\",\n    \"encrypt\",\n  ]\n}\n"
  },
  {
    "path": "tests/integration/terraform/azure/main.tf",
    "content": "terraform {\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp/azurerm\"\n      version = \">=3.20.0\"\n    }\n    azuread = {\n      source  = \"hashicorp/azuread\"\n      version = \">=2.28.0\"\n    }\n    azuredevops = {\n      source  = \"microsoft/azuredevops\"\n      version = \">=0.2.2\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nprovider \"azuredevops\" {\n  org_service_url       = \"https://dev.azure.com/${var.azuredevops_org}\"\n  personal_access_token = var.azuredevops_pat\n}\n\ndata \"azurerm_client_config\" \"current\" {}\n\nresource \"random_pet\" \"suffix\" {\n  separator = \"o\"\n}\n\nlocals {\n  name = \"e2e${random_pet.suffix.id}\"\n}\n"
  },
  {
    "path": "tests/integration/terraform/azure/outputs.tf",
    "content": "output \"aks_kubeconfig\" {\n  description = \"kubeconfig of the created AKS cluster\"\n  value       = module.aks.kubeconfig\n  sensitive   = true\n}\n\noutput \"azure_devops_access_token\" {\n  sensitive = true\n  value     = var.azuredevops_pat\n}\n\noutput \"fleet_infra_repository\" {\n  value = {\n    http = azuredevops_git_repository.fleet_infra.remote_url\n    ssh  = \"ssh://git@ssh.dev.azure.com/v3/${var.azuredevops_org}/${azuredevops_git_repository.fleet_infra.project_id}/${azuredevops_git_repository.fleet_infra.name}\"\n  }\n}\n\noutput \"application_repository\" {\n  value = {\n    http = azuredevops_git_repository.application.remote_url\n    ssh  = \"ssh://git@ssh.dev.azure.com/v3/${var.azuredevops_org}/${azuredevops_git_repository.application.project_id}/${azuredevops_git_repository.application.name}\"\n  }\n}\n\noutput \"aks_client_id\" {\n  value = module.aks.kubelet_client_id\n}\n\noutput \"event_hub_sas\" {\n  value     = azurerm_eventhub_authorization_rule.this.primary_connection_string\n  sensitive = true\n}\n\noutput \"sops_id\" {\n  value = azurerm_key_vault_key.sops.id\n}\n\noutput \"acr_url\" {\n  value = module.acr.registry_url\n}\n"
  },
  {
    "path": "tests/integration/terraform/azure/variables.tf",
    "content": "variable \"azuredevops_org\" {\n  type        = string\n  description = \"Name of Azure DevOps organizations were the repositories will be created\"\n}\n\nvariable \"azure_location\" {\n  type        = string\n  description = \"Location of the resource group\"\n  default     = \"eastus\"\n}\n\nvariable \"tags\" {\n  type        = map(string)\n  default     = {}\n  description = \"Tags for created Azure resources\"\n}\n\nvariable \"azuredevops_pat\" {\n  type        = string\n  description = \"Personal access token for Azure DevOps repository\"\n}\n"
  },
  {
    "path": "tests/integration/terraform/gcp/gke.tf",
    "content": "module \"gke\" {\n  source = \"git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/gke\"\n\n  name = local.name\n  tags = var.tags\n}\n\nmodule \"gcr\" {\n  source = \"git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/gcr\"\n\n  name = local.name\n  tags = var.tags\n}\n"
  },
  {
    "path": "tests/integration/terraform/gcp/kms.tf",
    "content": "data \"google_kms_key_ring\" \"keyring\" {\n  name     = var.gcp_keyring\n  location = \"global\"\n}\n\ndata \"google_kms_crypto_key\" \"my_crypto_key\" {\n  name     = var.gcp_crypto_key\n  key_ring = data.google_kms_key_ring.keyring.id\n}\n\nresource \"google_kms_key_ring_iam_binding\" \"key_ring\" {\n  key_ring_id = data.google_kms_key_ring.keyring.id\n  role        = \"roles/cloudkms.cryptoKeyEncrypterDecrypter\"\n\n  members = [\n    \"serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com\",\n  ]\n}\n"
  },
  {
    "path": "tests/integration/terraform/gcp/main.tf",
    "content": "provider \"google\" {\n  project = var.gcp_project_id\n  region  = var.gcp_region\n  zone    = var.gcp_zone\n}\n\nresource \"random_pet\" \"suffix\" {}\n\nlocals {\n  name = \"e2e-${random_pet.suffix.id}\"\n}\n\ndata \"google_project\" \"project\" {}\n"
  },
  {
    "path": "tests/integration/terraform/gcp/outputs.tf",
    "content": "output \"gke_kubeconfig\" {\n  value     = module.gke.kubeconfig\n  sensitive = true\n}\n\noutput \"gcp_project_id\" {\n  value = var.gcp_project_id\n}\n\noutput \"gcp_region\" {\n  value = var.gcp_region\n}\n\noutput \"artifact_registry_id\" {\n  value = module.gcr.artifact_repository_id\n}\n\noutput \"sops_id\" {\n  value = data.google_kms_crypto_key.my_crypto_key.id\n}\n\noutput \"fleet_infra_repository\" {\n  value = \"ssh://${var.gcp_email}@source.developers.google.com:2022/p/${var.gcp_project_id}/r/${google_sourcerepo_repository.fleet-infra.name}\"\n}\n\noutput \"application_repository\" {\n  value = \"ssh://${var.gcp_email}@source.developers.google.com:2022/p/${var.gcp_project_id}/r/${google_sourcerepo_repository.application.name}\"\n}\n\noutput \"pubsub_topic\" {\n  value = google_pubsub_topic.pubsub.name\n}\n"
  },
  {
    "path": "tests/integration/terraform/gcp/pubsub.tf",
    "content": "resource \"google_pubsub_topic\" \"pubsub\" {\n  name                       = local.name\n  labels                     = var.tags\n  message_retention_duration = \"7200s\"\n}\n\nresource \"google_pubsub_subscription\" \"sub\" {\n  project = var.gcp_project_id\n  name    = local.name\n  topic   = google_pubsub_topic.pubsub.name\n}\n"
  },
  {
    "path": "tests/integration/terraform/gcp/sourcerepo.tf",
    "content": "resource \"google_sourcerepo_repository\" \"fleet-infra\" {\n  name = \"fleet-infra-${random_pet.suffix.id}\"\n}\n\nresource \"google_sourcerepo_repository\" \"application\" {\n  name = \"application-${random_pet.suffix.id}\"\n}\n\nresource \"google_sourcerepo_repository_iam_binding\" \"application_binding\" {\n  project = google_sourcerepo_repository.application.project\n  repository = google_sourcerepo_repository.application.name\n  role = \"roles/source.admin\"\n  members = [\n    \"user:${var.gcp_email}\",\n  ]\n}\n\nresource \"google_sourcerepo_repository_iam_binding\" \"fleet-infra_binding\" {\n  project = google_sourcerepo_repository.fleet-infra.project\n  repository = google_sourcerepo_repository.fleet-infra.name\n  role = \"roles/source.admin\"\n  members = [\n    \"user:${var.gcp_email}\",\n  ]\n}\n\n"
  },
  {
    "path": "tests/integration/terraform/gcp/variables.tf",
    "content": "variable \"gcp_project_id\" {\n  type        = string\n  description = \"GCP project to create the resources in\"\n}\n\nvariable \"gcp_email\" {\n  type        = string\n  description = \"GCP user email\"\n}\n\nvariable \"gcp_region\" {\n  type        = string\n  default     = \"us-central1\"\n  description = \"GCP region\"\n}\n\nvariable \"gcp_zone\" {\n  type        = string\n  default     = \"us-central1\"\n  description = \"GCP zone\"\n}\n\nvariable \"gcp_keyring\" {\n  type        = string\n  description = \"GCP keyring that contains crypto key for encrypting secrets\"\n}\n\nvariable \"gcp_crypto_key\" {\n  type        = string\n  description = \"GCP crypto key for encrypting secrets\"\n}\n\nvariable \"tags\" {\n  type        = map(string)\n  default     = {}\n  description = \"tags for created resources\"\n}\n"
  },
  {
    "path": "tests/integration/util_test.go",
    "content": "/*\nCopyright 2023 The Flux authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\textgogit \"github.com/go-git/go-git/v5\"\n\t\"github.com/go-git/go-git/v5/plumbing\"\n\t\"github.com/go-git/go-git/v5/plumbing/object\"\n\t\"github.com/google/go-containerregistry/pkg/crane\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\tkerrors \"k8s.io/apimachinery/pkg/util/errors\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\thelmv2 \"github.com/fluxcd/helm-controller/api/v2\"\n\tautomationv1 \"github.com/fluxcd/image-automation-controller/api/v1\"\n\treflectorv1 \"github.com/fluxcd/image-reflector-controller/api/v1\"\n\tkustomizev1 \"github.com/fluxcd/kustomize-controller/api/v1\"\n\t\"github.com/fluxcd/pkg/apis/meta\"\n\t\"github.com/fluxcd/pkg/git\"\n\t\"github.com/fluxcd/pkg/git/gogit\"\n\t\"github.com/fluxcd/pkg/git/repository\"\n\t\"github.com/fluxcd/pkg/runtime/conditions\"\n\tsourcev1 \"github.com/fluxcd/source-controller/api/v1\"\n\t\"github.com/fluxcd/test-infra/tftestenv\"\n)\n\n// installFlux adds the core Flux components to the cluster specified in the kubeconfig file.\nfunc installFlux(ctx context.Context, tmpDir string, kubeconfigPath string) error {\n\t// Create flux-system namespace\n\tnamespace := corev1.Namespace{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"flux-system\",\n\t\t},\n\t}\n\terr := testEnv.Create(ctx, &namespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trepoURL := getTransportURL(cfg.fleetInfraRepository)\n\tif cfg.kustomizationYaml != \"\" {\n\t\tfiles := make(map[string]io.Reader)\n\t\tfiles[\"clusters/e2e/flux-system/kustomization.yaml\"] = strings.NewReader(cfg.kustomizationYaml)\n\t\tfiles[\"clusters/e2e/flux-system/gotk-components.yaml\"] = strings.NewReader(\"\")\n\t\tfiles[\"clusters/e2e/flux-system/gotk-sync.yaml\"] = strings.NewReader(\"\")\n\t\tc, err := getRepository(ctx, tmpDir, repoURL, defaultBranch, cfg.defaultAuthOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = commitAndPushAll(ctx, c, files, defaultBranch)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar bootstrapArgs string\n\tif cfg.defaultGitTransport == git.SSH {\n\t\tf, err := os.CreateTemp(\"\", \"flux-e2e-ssh-key-*\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = os.WriteFile(f.Name(), []byte(cfg.gitPrivateKey), 0o600)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbootstrapArgs = fmt.Sprintf(\"--private-key-file=%s -s\", f.Name())\n\t} else {\n\t\tbootstrapArgs = fmt.Sprintf(\"--token-auth --password=%s\", cfg.gitPat)\n\t}\n\n\tbootstrapCmd := fmt.Sprintf(\"%s bootstrap git  --url=%s %s --kubeconfig=%s --path=clusters/e2e \"+\n\t\t\" --components-extra image-reflector-controller,image-automation-controller\",\n\t\tfluxBin, repoURL, bootstrapArgs, kubeconfigPath)\n\n\treturn tftestenv.RunCommand(ctx, \"./\", bootstrapCmd, tftestenv.RunCommandOptions{\n\t\tTimeout: 15 * time.Minute,\n\t})\n}\n\nfunc runFluxCheck(ctx context.Context) error {\n\tcheckCmd := fmt.Sprintf(\"%s check --kubeconfig %s\", fluxBin, kubeconfigPath)\n\treturn tftestenv.RunCommand(ctx, \"./\", checkCmd, tftestenv.RunCommandOptions{\n\t\tAttachConsole: true,\n\t})\n}\n\nfunc uninstallFlux(ctx context.Context) error {\n\tuninstallCmd := fmt.Sprintf(\"%s uninstall --kubeconfig %s -s\", fluxBin, kubeconfigPath)\n\tif err := tftestenv.RunCommand(ctx, \"./\", uninstallCmd, tftestenv.RunCommandOptions{\n\t\tTimeout: 15 * time.Minute,\n\t}); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// verifyGitAndKustomization checks that the gitrespository and kustomization combination are working properly.\nfunc verifyGitAndKustomization(ctx context.Context, kubeClient client.Client, namespace, name string) error {\n\tnn := types.NamespacedName{\n\t\tName:      name,\n\t\tNamespace: namespace,\n\t}\n\tsource := &sourcev1.GitRepository{}\n\tif err := kubeClient.Get(ctx, nn, source); err != nil {\n\t\treturn err\n\t}\n\tif err := checkReadyCondition(source); err != nil {\n\t\treturn err\n\t}\n\n\tkustomization := &kustomizev1.Kustomization{}\n\tif err := kubeClient.Get(ctx, nn, kustomization); err != nil {\n\t\treturn err\n\t}\n\tif err := checkReadyCondition(kustomization); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\ntype nsConfig struct {\n\trepoURL      string\n\tref          *sourcev1.GitRepositoryRef\n\tprotocol     git.TransportType\n\tobjectName   string\n\tpath         string\n\tmodifyKsSpec func(spec *kustomizev1.KustomizationSpec)\n}\n\n// setUpFluxConfigs creates the namespace, then creates the git secret,\n// git repository and kustomization in that namespace\nfunc setUpFluxConfig(ctx context.Context, name string, opts nsConfig) error {\n\ttransport := cfg.defaultGitTransport\n\tif opts.protocol != \"\" {\n\t\ttransport = opts.protocol\n\t}\n\n\tnamespace := corev1.Namespace{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: name,\n\t\t},\n\t}\n\tif err := testEnv.Create(ctx, &namespace); err != nil && !apierrors.IsAlreadyExists(err) {\n\t\treturn err\n\t}\n\n\tsecret := corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"git-credentials\",\n\t\t\tNamespace: name,\n\t\t},\n\t}\n\n\tsecret.StringData = map[string]string{\n\t\t\"username\": cfg.gitUsername,\n\t\t\"password\": cfg.gitPat,\n\t}\n\n\tif transport == git.SSH {\n\t\tsecret.StringData = map[string]string{\n\t\t\t\"identity\":     cfg.gitPrivateKey,\n\t\t\t\"identity.pub\": cfg.gitPublicKey,\n\t\t\t\"known_hosts\":  cfg.knownHosts,\n\t\t}\n\t}\n\tif err := testEnv.Create(ctx, &secret); err != nil {\n\t\treturn err\n\t}\n\n\tref := &sourcev1.GitRepositoryRef{\n\t\tBranch: name,\n\t}\n\n\tif opts.ref != nil {\n\t\tref = opts.ref\n\t}\n\n\tgitSpec := &sourcev1.GitRepositorySpec{\n\t\tInterval: metav1.Duration{\n\t\t\tDuration: 1 * time.Minute,\n\t\t},\n\t\tReference: ref,\n\t\tSecretRef: &meta.LocalObjectReference{\n\t\t\tName: secret.Name,\n\t\t},\n\t\tURL: opts.repoURL,\n\t}\n\n\tsource := &sourcev1.GitRepository{\n\t\tObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name},\n\t\tSpec:       *gitSpec,\n\t}\n\tif err := testEnv.Create(ctx, source); err != nil {\n\t\treturn err\n\t}\n\n\tksSpec := &kustomizev1.KustomizationSpec{\n\t\tPath:            opts.path,\n\t\tTargetNamespace: name,\n\t\tSourceRef: kustomizev1.CrossNamespaceSourceReference{\n\t\t\tKind:      sourcev1.GitRepositoryKind,\n\t\t\tName:      source.Name,\n\t\t\tNamespace: source.Namespace,\n\t\t},\n\t\tInterval: metav1.Duration{\n\t\t\tDuration: 1 * time.Minute,\n\t\t},\n\t\tPrune: true,\n\t}\n\tif opts.modifyKsSpec != nil {\n\t\topts.modifyKsSpec(ksSpec)\n\t}\n\tkustomization := &kustomizev1.Kustomization{\n\t\tObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name},\n\t\tSpec:       *ksSpec,\n\t}\n\n\treturn testEnv.Create(ctx, kustomization)\n}\n\nfunc tearDownFluxConfig(ctx context.Context, name string) error {\n\tvar allErr []error\n\n\tsource := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}\n\tif err := testEnv.Delete(ctx, source); err != nil {\n\t\tallErr = append(allErr, err)\n\t}\n\n\tkustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}\n\tif err := testEnv.Delete(ctx, kustomization); err != nil {\n\t\tallErr = append(allErr, err)\n\t}\n\n\tnamespace := corev1.Namespace{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: name,\n\t\t},\n\t}\n\tif err := testEnv.Delete(ctx, &namespace); err != nil {\n\t\tallErr = append(allErr, err)\n\t}\n\n\treturn kerrors.NewAggregate(allErr)\n}\n\n// getRepository and clones the git repository to the directory.\nfunc getRepository(ctx context.Context, dir, repoURL, branchName string, authOpts *git.AuthOptions) (*gogit.Client, error) {\n\tc, err := gogit.NewClient(dir, authOpts, gogit.WithSingleBranch(false), gogit.WithDiskStorage())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = c.Clone(ctx, repoURL, repository.CloneConfig{\n\t\tCheckoutStrategy: repository.CheckoutStrategy{\n\t\t\tBranch: branchName,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn c, nil\n}\n\n// commitAndPushAll checks out to the specified branch, creates the files, commits and then pushes them to\n// the remote git repository.\nfunc commitAndPushAll(ctx context.Context, client *gogit.Client, files map[string]io.Reader, branchName string) error {\n\terr := client.SwitchBranch(ctx, branchName)\n\tif err != nil && !errors.Is(err, plumbing.ErrReferenceNotFound) {\n\t\treturn err\n\t}\n\n\t_, err = client.Commit(git.Commit{\n\t\tAuthor: git.Signature{\n\t\t\tName:  git.DefaultPublicKeyAuthUser,\n\t\t\tEmail: \"test@example.com\",\n\t\t\tWhen:  time.Now(),\n\t\t},\n\t}, repository.WithFiles(files))\n\tif err != nil {\n\t\tif errors.Is(err, git.ErrNoStagedFiles) {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn err\n\t}\n\n\terr = client.Push(ctx, repository.PushConfig{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to push: %s\", err)\n\t}\n\n\treturn nil\n}\n\nfunc createTagAndPush(ctx context.Context, client *gogit.Client, branchName, newTag string) error {\n\trepo, err := extgogit.PlainOpen(client.Path())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tref, err := repo.Reference(plumbing.NewBranchReferenceName(branchName), false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttags, err := repo.TagObjects()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = tags.ForEach(func(tag *object.Tag) error {\n\t\tif tag.Name == newTag {\n\t\t\terr = repo.DeleteTag(tag.Name)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error deleting local tag: %w\", err)\n\t}\n\n\t// Delete remote tag\n\tif err := client.Push(ctx, repository.PushConfig{\n\t\tRefspecs: []string{fmt.Sprintf(\":refs/tags/%s\", newTag)},\n\t\tForce:    true,\n\t}); err != nil && !errors.Is(err, extgogit.NoErrAlreadyUpToDate) {\n\t\treturn fmt.Errorf(\"unable to delete existing tag: %w\", err)\n\t}\n\n\tsig := &object.Signature{\n\t\tName:  git.DefaultPublicKeyAuthUser,\n\t\tEmail: \"test@example.com\",\n\t\tWhen:  time.Now(),\n\t}\n\tif _, err = repo.CreateTag(newTag, ref.Hash(), &extgogit.CreateTagOptions{\n\t\tTagger:  sig,\n\t\tMessage: \"create tag\",\n\t}); err != nil {\n\t\treturn fmt.Errorf(\"unable to create tag: %w\", err)\n\t}\n\n\treturn client.Push(ctx, repository.PushConfig{\n\t\tRefspecs: []string{\"refs/tags/*:refs/tags/*\"},\n\t})\n}\n\nfunc pushImagesFromURL(repoURL, imgURL string, tags []string) error {\n\timg, err := crane.Pull(imgURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, tag := range tags {\n\t\tif err := crane.Push(img, fmt.Sprintf(\"%s:%s\", repoURL, tag)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc getTransportURL(urls gitUrl) string {\n\tif cfg.defaultGitTransport == git.SSH {\n\t\treturn urls.ssh\n\t}\n\n\treturn urls.http\n}\n\nfunc authOpts(repoURL string, authData map[string][]byte) (*git.AuthOptions, error) {\n\tu, err := url.Parse(repoURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn git.NewAuthOptions(*u, authData)\n}\n\n// checkReadyCondition checks for a Ready condition, it returns nil if the condition is true\n// or an error (with the message if the Ready condition is present).\nfunc checkReadyCondition(from conditions.Getter) error {\n\tif conditions.IsReady(from) {\n\t\treturn nil\n\t}\n\terrMsg := \"object not ready\"\n\treadyMsg := conditions.GetMessage(from, meta.ReadyCondition)\n\tif readyMsg != \"\" {\n\t\terrMsg += \": \" + readyMsg\n\t}\n\treturn errors.New(errMsg)\n}\n\n// dumpDiagnostics prints Flux object states and controller logs when a test\n// has failed. It should be registered via t.Cleanup so that it runs after the\n// test body completes.\nfunc dumpDiagnostics(t *testing.T, ctx context.Context, namespace string) {\n\tt.Helper()\n\tif !t.Failed() {\n\t\treturn\n\t}\n\n\tt.Log(\"=== Diagnostics dump (test failed) ===\")\n\tdumpFluxObjects(t, ctx, namespace)\n\tdumpControllerLogs(t, ctx)\n\tt.Log(\"=== End diagnostics dump ===\")\n}\n\n// dumpFluxObjects lists Flux custom resources in the given namespace and prints\n// their status conditions.\nfunc dumpFluxObjects(t *testing.T, ctx context.Context, namespace string) {\n\tt.Helper()\n\tlistOpts := &client.ListOptions{Namespace: namespace}\n\n\tgitRepos := &sourcev1.GitRepositoryList{}\n\tif err := testEnv.List(ctx, gitRepos, listOpts); err == nil {\n\t\tfor _, r := range gitRepos.Items {\n\t\t\tlogObjectStatus(t, \"GitRepository\", r.Name, r.Namespace, r.Status.Conditions)\n\t\t}\n\t}\n\n\thelmRepos := &sourcev1.HelmRepositoryList{}\n\tif err := testEnv.List(ctx, helmRepos, listOpts); err == nil {\n\t\tfor _, r := range helmRepos.Items {\n\t\t\tlogObjectStatus(t, \"HelmRepository\", r.Name, r.Namespace, r.Status.Conditions)\n\t\t}\n\t}\n\n\thelmCharts := &sourcev1.HelmChartList{}\n\tif err := testEnv.List(ctx, helmCharts, listOpts); err == nil {\n\t\tfor _, r := range helmCharts.Items {\n\t\t\tlogObjectStatus(t, \"HelmChart\", r.Name, r.Namespace, r.Status.Conditions)\n\t\t}\n\t}\n\n\tkustomizations := &kustomizev1.KustomizationList{}\n\tif err := testEnv.List(ctx, kustomizations, listOpts); err == nil {\n\t\tfor _, r := range kustomizations.Items {\n\t\t\tlogObjectStatus(t, \"Kustomization\", r.Name, r.Namespace, r.Status.Conditions)\n\t\t}\n\t}\n\n\thelmReleases := &helmv2.HelmReleaseList{}\n\tif err := testEnv.List(ctx, helmReleases, listOpts); err == nil {\n\t\tfor _, r := range helmReleases.Items {\n\t\t\tlogObjectStatus(t, \"HelmRelease\", r.Name, r.Namespace, r.Status.Conditions)\n\t\t}\n\t}\n\n\timageRepos := &reflectorv1.ImageRepositoryList{}\n\tif err := testEnv.List(ctx, imageRepos, listOpts); err == nil {\n\t\tfor _, r := range imageRepos.Items {\n\t\t\tlogObjectStatus(t, \"ImageRepository\", r.Name, r.Namespace, r.Status.Conditions)\n\t\t}\n\t}\n\n\timagePolicies := &reflectorv1.ImagePolicyList{}\n\tif err := testEnv.List(ctx, imagePolicies, listOpts); err == nil {\n\t\tfor _, r := range imagePolicies.Items {\n\t\t\tlogObjectStatus(t, \"ImagePolicy\", r.Name, r.Namespace, r.Status.Conditions)\n\t\t}\n\t}\n\n\timageAutomations := &automationv1.ImageUpdateAutomationList{}\n\tif err := testEnv.List(ctx, imageAutomations, listOpts); err == nil {\n\t\tfor _, r := range imageAutomations.Items {\n\t\t\tlogObjectStatus(t, \"ImageUpdateAutomation\", r.Name, r.Namespace, r.Status.Conditions)\n\t\t}\n\t}\n}\n\n// logObjectStatus prints the status conditions of a Flux object.\nfunc logObjectStatus(t *testing.T, kind, name, namespace string, conditions []metav1.Condition) {\n\tt.Helper()\n\tt.Logf(\"  %s/%s (ns: %s):\", kind, name, namespace)\n\tfor _, c := range conditions {\n\t\tt.Logf(\"    %s: %s — %s (since %s)\", c.Type, c.Status, c.Message, c.LastTransitionTime.Format(time.RFC3339))\n\t}\n}\n\n// dumpControllerLogs prints the logs of all Flux controller pods in the\n// flux-system namespace.\nfunc dumpControllerLogs(t *testing.T, ctx context.Context) {\n\tt.Helper()\n\n\tpodList, err := testEnv.ClientGo.CoreV1().Pods(\"flux-system\").List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\tt.Logf(\"failed to list flux-system pods: %v\", err)\n\t\treturn\n\t}\n\n\tfor _, pod := range podList.Items {\n\t\tlogs, err := testEnv.ClientGo.\n\t\t\tCoreV1().\n\t\t\tPods(pod.Namespace).\n\t\t\tGetLogs(pod.Name, &corev1.PodLogOptions{}).\n\t\t\tDoRaw(ctx)\n\t\tif err != nil {\n\t\t\tt.Logf(\"failed to get logs for pod %s: %v\", pod.Name, err)\n\t\t\tcontinue\n\t\t}\n\t\tt.Logf(\"=== Logs for pod %s ===\\n%s\", pod.Name, string(logs))\n\t}\n}\n\n// logNamespacePods logs the state of all pods in the given namespace,\n// including container statuses and recent events. Useful for understanding\n// why a Helm install is stuck.\nfunc logNamespacePods(t *testing.T, ctx context.Context, namespace string) {\n\tt.Helper()\n\n\tpodList := &corev1.PodList{}\n\tif err := testEnv.List(ctx, podList, &client.ListOptions{Namespace: namespace}); err != nil {\n\t\tt.Logf(\"  failed to list pods in %s: %v\", namespace, err)\n\t\treturn\n\t}\n\tif len(podList.Items) == 0 {\n\t\tt.Logf(\"  no pods in namespace %s\", namespace)\n\t\treturn\n\t}\n\tfor _, pod := range podList.Items {\n\t\tt.Logf(\"  pod %s: phase=%s\", pod.Name, pod.Status.Phase)\n\t\tfor _, cs := range pod.Status.ContainerStatuses {\n\t\t\tif cs.State.Waiting != nil {\n\t\t\t\tt.Logf(\"    container %s: waiting — %s: %s\", cs.Name, cs.State.Waiting.Reason, cs.State.Waiting.Message)\n\t\t\t} else if cs.State.Terminated != nil {\n\t\t\t\tt.Logf(\"    container %s: terminated — %s (exit %d)\", cs.Name, cs.State.Terminated.Reason, cs.State.Terminated.ExitCode)\n\t\t\t} else if cs.State.Running != nil {\n\t\t\t\tt.Logf(\"    container %s: running (ready=%v)\", cs.Name, cs.Ready)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Log recent events in the namespace for scheduling/pull failures.\n\tevents, err := testEnv.ClientGo.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\tt.Logf(\"  failed to list events in %s: %v\", namespace, err)\n\t\treturn\n\t}\n\tif len(events.Items) > 0 {\n\t\tt.Logf(\"  events in namespace %s:\", namespace)\n\t\tfor _, e := range events.Items {\n\t\t\tt.Logf(\"    %s %s/%s: %s — %s\", e.Type, e.InvolvedObject.Kind, e.InvolvedObject.Name, e.Reason, e.Message)\n\t\t}\n\t}\n}\n"
  }
]