diff --git a/.github/aur/flux-bin/.SRCINFO.template b/.github/aur/flux-bin/.SRCINFO.template index b75d3e621e..d09772f61f 100644 --- a/.github/aur/flux-bin/.SRCINFO.template +++ b/.github/aur/flux-bin/.SRCINFO.template @@ -4,19 +4,16 @@ pkgbase = flux-bin pkgrel = ${PKGREL} url = https://fluxcd.io/ arch = x86_64 - arch = armv6h arch = armv7h arch = aarch64 license = APACHE optdepends = bash-completion: auto-completion for flux in Bash optdepends = zsh-completions: auto-completion for flux in ZSH - source_x86_64 = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_amd64.tar.gz + source_x86_64 = flux-bin-${PKGVER}_linux_amd64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_amd64.tar.gz sha256sums_x86_64 = ${SHA256SUM_AMD64} - source_armv6h = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm.tar.gz - sha256sums_armv6h = ${SHA256SUM_ARM} - source_armv7h = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm.tar.gz + source_armv7h = flux-bin-${PKGVER}_linux_arm.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm.tar.gz sha256sums_armv7h = ${SHA256SUM_ARM} - source_aarch64 = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm64.tar.gz + source_aarch64 = flux-bin-${PKGVER}_linux_arm64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm64.tar.gz sha256sums_aarch64 = ${SHA256SUM_ARM64} pkgname = flux-bin diff --git a/.github/aur/flux-bin/PKGBUILD.template b/.github/aur/flux-bin/PKGBUILD.template index c390bd51de..f011e1f98b 100644 --- a/.github/aur/flux-bin/PKGBUILD.template +++ b/.github/aur/flux-bin/PKGBUILD.template @@ -8,28 +8,22 @@ _srcname=flux _srcver=${VERSION} pkgdesc="Open and extensible continuous delivery solution for Kubernetes" url="https://fluxcd.io/" -arch=("x86_64" "armv6h" "armv7h" "aarch64") +arch=("x86_64" "armv7h" "aarch64") license=("APACHE") optdepends=('bash-completion: auto-completion for flux in Bash' 'zsh-completions: auto-completion for flux in ZSH') source_x86_64=( - "${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_amd64.tar.gz" -) -source_armv6h=( - "${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm.tar.gz" + "${pkgname}-${pkgver}_linux_amd64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_amd64.tar.gz" ) source_armv7h=( - "${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm.tar.gz" + "${pkgname}-${pkgver}_linux_arm.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm.tar.gz" ) source_aarch64=( - "${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm64.tar.gz" + "${pkgname}-${pkgver}_linux_arm64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm64.tar.gz" ) sha256sums_x86_64=( ${SHA256SUM_AMD64} ) -sha256sums_armv6h=( - ${SHA256SUM_ARM} -) sha256sums_armv7h=( ${SHA256SUM_ARM} ) diff --git a/.github/aur/flux-go/.SRCINFO.template b/.github/aur/flux-go/.SRCINFO.template index d41a9e3460..134f969cf3 100644 --- a/.github/aur/flux-go/.SRCINFO.template +++ b/.github/aur/flux-go/.SRCINFO.template @@ -4,7 +4,6 @@ pkgbase = flux-go pkgrel = ${PKGREL} url = https://fluxcd.io/ arch = x86_64 - arch = armv6h arch = armv7h arch = aarch64 license = APACHE diff --git a/.github/aur/flux-go/PKGBUILD.template b/.github/aur/flux-go/PKGBUILD.template index 05372e13b4..6125498ac2 100644 --- a/.github/aur/flux-go/PKGBUILD.template +++ b/.github/aur/flux-go/PKGBUILD.template @@ -8,7 +8,7 @@ _srcname=flux _srcver=${VERSION} pkgdesc="Open and extensible continuous delivery solution for Kubernetes" url="https://fluxcd.io/" -arch=("x86_64" "armv6h" "armv7h" "aarch64") +arch=("x86_64" "armv7h" "aarch64") license=("APACHE") provides=("flux-bin") conflicts=("flux-bin") @@ -41,7 +41,7 @@ check() { aarch64) export ENVTEST_ARCH=arm64 ;; - armv6h|armv7h) + armv7h) export ENVTEST_ARCH=arm ;; esac diff --git a/.github/aur/flux-scm/.SRCINFO.template b/.github/aur/flux-scm/.SRCINFO.template index 343c7ce215..6237e6727b 100644 --- a/.github/aur/flux-scm/.SRCINFO.template +++ b/.github/aur/flux-scm/.SRCINFO.template @@ -4,7 +4,6 @@ pkgbase = flux-scm pkgrel = ${PKGREL} url = https://fluxcd.io/ arch = x86_64 - arch = armv6h arch = armv7h arch = aarch64 license = APACHE diff --git a/.github/aur/flux-scm/PKGBUILD.template b/.github/aur/flux-scm/PKGBUILD.template index 6e213ac2a6..dc0917143c 100644 --- a/.github/aur/flux-scm/PKGBUILD.template +++ b/.github/aur/flux-scm/PKGBUILD.template @@ -7,7 +7,7 @@ pkgrel=${PKGREL} _srcname=flux pkgdesc="Open and extensible continuous delivery solution for Kubernetes" url="https://fluxcd.io/" -arch=("x86_64" "armv6h" "armv7h" "aarch64") +arch=("x86_64" "armv7h" "aarch64") license=("APACHE") provides=("flux-bin") conflicts=("flux-bin") @@ -42,7 +42,7 @@ check() { aarch64) export ENVTEST_ARCH=arm64 ;; - armv6h|armv7h) + armv7h) export ENVTEST_ARCH=arm ;; esac diff --git a/.github/kind/config.yaml b/.github/kind/config.yaml index e2834d6c37..12c4c2d96e 100644 --- a/.github/kind/config.yaml +++ b/.github/kind/config.yaml @@ -1,5 +1,9 @@ kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + - role: worker + - role: worker networking: disableDefaultCNI: true # disable kindnet podSubnet: 192.168.0.0/16 # set to Calico's default subnet diff --git a/.github/labels.yaml b/.github/labels.yaml index c83f0bd93e..58c66d1ef6 100644 --- a/.github/labels.yaml +++ b/.github/labels.yaml @@ -47,3 +47,12 @@ - name: backport:release/v2.0.x description: To be backported to release/v2.0.x color: '#ffd700' +- name: backport:release/v2.1.x + description: To be backported to release/v2.1.x + color: '#ffd700' +- name: backport:release/v2.2.x + description: To be backported to release/v2.2.x + color: '#ffd700' +- name: backport:release/v2.3.x + description: To be backported to release/v2.3.x + color: '#ffd700' diff --git a/.github/runners/README.md b/.github/runners/README.md index 440c686519..8dc738f139 100644 --- a/.github/runners/README.md +++ b/.github/runners/README.md @@ -4,16 +4,18 @@ The Flux ARM64 end-to-end tests run on Equinix Metal instances provisioned with ## Current instances -| Repository | Runner | Instance | Location | -|-----------------------------|------------------|------------------------|---------------| -| flux2 | equinix-arm-dc-1 | flux-equinix-arm-dc-01 | Washington DC | -| flux2 | equinix-arm-dc-2 | flux-equinix-arm-dc-01 | Washington DC | -| flux2 | equinix-arm-da-1 | flux-equinix-arm-da-01 | Dallas | -| flux2 | equinix-arm-da-2 | flux-equinix-arm-da-01 | Dallas | -| source-controller | equinix-arm-dc-1 | flux-equinix-arm-dc-01 | Washington DC | -| source-controller | equinix-arm-da-1 | flux-equinix-arm-da-01 | Dallas | -| image-automation-controller | equinix-arm-dc-1 | flux-equinix-arm-dc-01 | Washington DC | -| image-automation-controller | equinix-arm-da-1 | flux-equinix-arm-da-01 | Dallas | +| Repository | Runner | Instance | Location | +|-----------------------------|------------------|----------------|---------------| +| flux2 | equinix-arm-dc-1 | flux-arm-dc-01 | Washington DC | +| flux2 | equinix-arm-dc-2 | flux-arm-dc-01 | Washington DC | +| flux2 | equinix-arm-da-1 | flux-arm-da-01 | Dallas | +| flux2 | equinix-arm-da-2 | flux-arm-da-01 | Dallas | +| flux-benchmark | equinix-arm-dc-1 | flux-arm-dc-01 | Washington DC | +| flux-benchmark | equinix-arm-da-1 | flux-arm-da-01 | Dallas | +| source-controller | equinix-arm-dc-1 | flux-arm-dc-01 | Washington DC | +| source-controller | equinix-arm-da-1 | flux-arm-da-01 | Dallas | +| image-automation-controller | equinix-arm-dc-1 | flux-arm-dc-01 | Washington DC | +| image-automation-controller | equinix-arm-da-1 | flux-arm-da-01 | Dallas | Instance spec: - Ampere Altra Q80-30 80-core processor @ 2.8GHz diff --git a/.github/runners/prereq.sh b/.github/runners/prereq.sh index 043876eba3..e1e497f40a 100755 --- a/.github/runners/prereq.sh +++ b/.github/runners/prereq.sh @@ -18,11 +18,11 @@ set -eu -KIND_VERSION=0.17.0 -KUBECTL_VERSION=1.24.0 -KUSTOMIZE_VERSION=4.5.7 -HELM_VERSION=3.10.1 -GITHUB_RUNNER_VERSION=2.298.2 +KIND_VERSION=0.22.0 +KUBECTL_VERSION=1.29.0 +KUSTOMIZE_VERSION=5.3.0 +HELM_VERSION=3.14.1 +GITHUB_RUNNER_VERSION=2.313.0 PACKAGES="apt-transport-https ca-certificates software-properties-common build-essential libssl-dev gnupg lsb-release jq pkg-config" # install prerequisites diff --git a/.github/runners/runner-setup.sh b/.github/runners/runner-setup.sh index b13d87e2a8..e7e1d3321d 100755 --- a/.github/runners/runner-setup.sh +++ b/.github/runners/runner-setup.sh @@ -22,7 +22,7 @@ RUNNER_NAME=$1 REPOSITORY_TOKEN=$2 REPOSITORY_URL=${3:-https://github.com/fluxcd/flux2} -GITHUB_RUNNER_VERSION=2.298.2 +GITHUB_RUNNER_VERSION=2.313.0 # download runner curl -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 \ diff --git a/.github/workflows/action.yaml b/.github/workflows/action.yaml index cdcb6c553c..1e49a6a662 100644 --- a/.github/workflows/action.yaml +++ b/.github/workflows/action.yaml @@ -24,6 +24,6 @@ jobs: name: action on ${{ matrix.version }} steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup flux uses: ./action diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index 70f2e31ef9..0659f7891e 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -13,11 +13,11 @@ jobs: if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name)) steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs - uses: korthout/backport-action@bf5fdd624b35f95d5b85991a728bd5744e8c6cf2 # v1.3.1 + uses: korthout/backport-action@ef20d86abccbac3ee3a73cb2efbdc06344c390e5 # v2.5.0 # xref: https://github.com/korthout/backport-action#inputs with: # Use token to allow workflows to be triggered for the created PR diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml new file mode 100644 index 0000000000..2086a6c8a7 --- /dev/null +++ b/.github/workflows/conformance.yaml @@ -0,0 +1,267 @@ +name: conformance + +on: + workflow_dispatch: + push: + branches: [ 'main', 'update-components', 'release/**', 'conform*' ] + +permissions: + contents: read + +env: + GO_VERSION: 1.22.x + +jobs: + conform-kubernetes: + # Hosted on Equinix + # Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners + runs-on: [self-hosted, Linux, ARM64, equinix] + strategy: + matrix: + # Keep this list up-to-date with https://endoflife.date/kubernetes + # Build images with https://github.com/fluxcd/flux-benchmark/actions/workflows/build-kind.yaml + KUBERNETES_VERSION: [ 1.28.9, 1.29.4, 1.30.0 ] + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - name: Setup Go + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: | + **/go.sum + **/go.mod + - name: Prepare + id: prep + run: | + ID=${GITHUB_SHA:0:7}-${{ matrix.KUBERNETES_VERSION }}-$(date +%s) + echo "CLUSTER=arm64-${ID}" >> $GITHUB_OUTPUT + - name: Build + run: | + make build + - name: Setup Kubernetes Kind + run: | + kind create cluster \ + --wait 5m \ + --name ${{ steps.prep.outputs.CLUSTER }} \ + --kubeconfig=/tmp/${{ steps.prep.outputs.CLUSTER }} \ + --image=ghcr.io/fluxcd/kindest/node:v${{ matrix.KUBERNETES_VERSION }}-arm64 + - name: Run e2e tests + run: TEST_KUBECONFIG=/tmp/${{ steps.prep.outputs.CLUSTER }} make e2e + - name: Run multi-tenancy tests + env: + KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }} + run: | + ./bin/flux install + ./bin/flux create source git flux-system \ + --interval=15m \ + --url=https://github.com/fluxcd/flux2-multi-tenancy \ + --branch=main \ + --ignore-paths="./clusters/**/flux-system/" + ./bin/flux create kustomization flux-system \ + --interval=15m \ + --source=flux-system \ + --path=./clusters/staging + kubectl -n flux-system wait kustomization/tenants --for=condition=ready --timeout=5m + kubectl -n apps wait kustomization/dev-team --for=condition=ready --timeout=1m + kubectl -n apps wait helmrelease/podinfo --for=condition=ready --timeout=1m + - name: Debug failure + if: failure() + env: + KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }} + run: | + kubectl -n flux-system get all + kubectl -n flux-system describe po + kubectl -n flux-system logs deploy/source-controller + kubectl -n flux-system logs deploy/kustomize-controller + - name: Cleanup + if: always() + run: | + kind delete cluster --name ${{ steps.prep.outputs.CLUSTER }} + rm /tmp/${{ steps.prep.outputs.CLUSTER }} + + conform-k3s: + runs-on: ubuntu-latest + strategy: + matrix: + # Keep this list up-to-date with https://endoflife.date/kubernetes + # Available versions can be found with "replicated cluster versions" + K3S_VERSION: [ 1.28.7, 1.29.2 ] + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - name: Setup Go + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: | + **/go.sum + **/go.mod + - name: Prepare + id: prep + run: | + ID=${GITHUB_SHA:0:7}-${{ matrix.K3S_VERSION }}-$(date +%s) + PSEUDO_RAND_SUFFIX=$(echo "${ID}" | shasum | awk '{print $1}') + echo "cluster=flux2-k3s-${PSEUDO_RAND_SUFFIX}" >> $GITHUB_OUTPUT + KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml" + echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT + - name: Setup Kustomize + uses: fluxcd/pkg/actions/kustomize@main + - name: Build + run: make build-dev + - name: Create repository + run: | + gh repo create --private --add-readme fluxcd-testing/${{ steps.prep.outputs.cluster }} + env: + GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} + - name: Create cluster + id: create-cluster + uses: replicatedhq/compatibility-actions/create-cluster@v1 + with: + api-token: ${{ secrets.REPLICATED_API_TOKEN }} + kubernetes-distribution: "k3s" + kubernetes-version: ${{ matrix.K3S_VERSION }} + ttl: 20m + cluster-name: "${{ steps.prep.outputs.cluster }}" + kubeconfig-path: ${{ steps.prep.outputs.kubeconfig-path }} + export-kubeconfig: true + - name: Run e2e tests + run: TEST_KUBECONFIG=${{ steps.prep.outputs.kubeconfig-path }} make e2e + - name: Run flux bootstrap + run: | + ./bin/flux bootstrap git --manifests ./manifests/install/ \ + --components-extra=image-reflector-controller,image-automation-controller \ + --url=https://github.com/fluxcd-testing/${{ steps.prep.outputs.cluster }} \ + --branch=main \ + --path=clusters/k3s \ + --token-auth + env: + GIT_PASSWORD: ${{ secrets.GITPROVIDER_BOT_TOKEN }} + - name: Run flux check + run: | + ./bin/flux check + - name: Run flux reconcile + run: | + ./bin/flux reconcile ks flux-system --with-source + ./bin/flux get all + ./bin/flux events + - name: Collect reconcile logs + if: ${{ always() }} + continue-on-error: true + run: | + kubectl -n flux-system get all + kubectl -n flux-system describe pods + kubectl -n flux-system logs deploy/source-controller + kubectl -n flux-system logs deploy/kustomize-controller + kubectl -n flux-system logs deploy/notification-controller + - name: Delete flux + run: | + ./bin/flux uninstall -s --keep-namespace + kubectl delete ns flux-system --wait + - name: Delete cluster + if: ${{ always() }} + uses: replicatedhq/replicated-actions/remove-cluster@v1 + continue-on-error: true + with: + api-token: ${{ secrets.REPLICATED_API_TOKEN }} + cluster-id: ${{ steps.create-cluster.outputs.cluster-id }} + - name: Delete repository + if: ${{ always() }} + continue-on-error: true + run: | + gh repo delete fluxcd-testing/${{ steps.prep.outputs.cluster }} --yes + env: + GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} + + conform-openshift: + runs-on: ubuntu-latest + strategy: + matrix: + # Keep this list up-to-date with https://endoflife.date/red-hat-openshift + OPENSHIFT_VERSION: [ 4.15.0-okd ] + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - name: Setup Go + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: | + **/go.sum + **/go.mod + - name: Prepare + id: prep + run: | + ID=${GITHUB_SHA:0:7}-${{ matrix.OPENSHIFT_VERSION }}-$(date +%s) + PSEUDO_RAND_SUFFIX=$(echo "${ID}" | shasum | awk '{print $1}') + echo "cluster=flux2-openshift-${PSEUDO_RAND_SUFFIX}" >> $GITHUB_OUTPUT + KUBECONFIG_PATH="$(git rev-parse --show-toplevel)/bin/kubeconfig.yaml" + echo "kubeconfig-path=${KUBECONFIG_PATH}" >> $GITHUB_OUTPUT + - name: Setup Kustomize + uses: fluxcd/pkg/actions/kustomize@main + - name: Build + run: make build-dev + - name: Create repository + run: | + gh repo create --private --add-readme fluxcd-testing/${{ steps.prep.outputs.cluster }} + env: + GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} + - name: Create cluster + id: create-cluster + uses: replicatedhq/compatibility-actions/create-cluster@v1 + with: + api-token: ${{ secrets.REPLICATED_API_TOKEN }} + kubernetes-distribution: "openshift" + kubernetes-version: ${{ matrix.OPENSHIFT_VERSION }} + ttl: 20m + cluster-name: "${{ steps.prep.outputs.cluster }}" + kubeconfig-path: ${{ steps.prep.outputs.kubeconfig-path }} + export-kubeconfig: true + - name: Run flux bootstrap + run: | + ./bin/flux bootstrap git --manifests ./manifests/openshift/ \ + --components-extra=image-reflector-controller,image-automation-controller \ + --url=https://github.com/fluxcd-testing/${{ steps.prep.outputs.cluster }} \ + --branch=main \ + --path=clusters/openshift \ + --token-auth + env: + GIT_PASSWORD: ${{ secrets.GITPROVIDER_BOT_TOKEN }} + - name: Run flux check + run: | + ./bin/flux check + - name: Run flux reconcile + run: | + ./bin/flux reconcile ks flux-system --with-source + ./bin/flux get all + ./bin/flux events + - name: Collect reconcile logs + if: ${{ always() }} + continue-on-error: true + run: | + kubectl -n flux-system get all + kubectl -n flux-system describe pods + kubectl -n flux-system logs deploy/source-controller + kubectl -n flux-system logs deploy/kustomize-controller + kubectl -n flux-system logs deploy/notification-controller + - name: Delete flux + run: | + ./bin/flux uninstall -s --keep-namespace + kubectl delete ns flux-system --wait + - name: Delete cluster + if: ${{ always() }} + uses: replicatedhq/replicated-actions/remove-cluster@v1 + continue-on-error: true + with: + api-token: ${{ secrets.REPLICATED_API_TOKEN }} + cluster-id: ${{ steps.create-cluster.outputs.cluster-id }} + - name: Delete repository + if: ${{ always() }} + continue-on-error: true + run: | + gh repo delete fluxcd-testing/${{ steps.prep.outputs.cluster }} --yes + env: + GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} diff --git a/.github/workflows/e2e-arm64.yaml b/.github/workflows/e2e-arm64.yaml deleted file mode 100644 index ee800cf28b..0000000000 --- a/.github/workflows/e2e-arm64.yaml +++ /dev/null @@ -1,105 +0,0 @@ -name: e2e-arm64 - -on: - workflow_dispatch: - push: - branches: [ 'main', 'update-components', 'e2e-*', 'release/**' ] - -permissions: - contents: read - -jobs: - e2e-arm64-kubernetes: - # Hosted on Equinix - # Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners - runs-on: [self-hosted, Linux, ARM64, equinix] - strategy: - matrix: - # Keep this list up-to-date with https://endoflife.date/kubernetes - # Check which versions are available on DockerHub with 'crane ls kindest/node' - KUBERNETES_VERSION: [ 1.25.8, 1.26.3, 1.27.3 ] - steps: - - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - name: Setup Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 - with: - go-version: 1.20.x - cache-dependency-path: | - **/go.sum - **/go.mod - - name: Prepare - id: prep - run: | - ID=${GITHUB_SHA:0:7}-${{ matrix.KUBERNETES_VERSION }}-$(date +%s) - echo "CLUSTER=arm64-${ID}" >> $GITHUB_OUTPUT - - name: Build - run: | - make build - - name: Setup Kubernetes Kind - run: | - kind create cluster \ - --wait 5m \ - --name ${{ steps.prep.outputs.CLUSTER }} \ - --kubeconfig=/tmp/${{ steps.prep.outputs.CLUSTER }} \ - --image=kindest/node:v${{ matrix.KUBERNETES_VERSION }} - - name: Run e2e tests - run: TEST_KUBECONFIG=/tmp/${{ steps.prep.outputs.CLUSTER }} make e2e - - name: Run multi-tenancy tests - env: - KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }} - run: | - ./bin/flux install - ./bin/flux create source git flux-system \ - --interval=15m \ - --url=https://github.com/fluxcd/flux2-multi-tenancy \ - --branch=main \ - --ignore-paths="./clusters/**/flux-system/" - ./bin/flux create kustomization flux-system \ - --interval=15m \ - --source=flux-system \ - --path=./clusters/staging - kubectl -n flux-system wait kustomization/tenants --for=condition=ready --timeout=5m - kubectl -n apps wait kustomization/dev-team --for=condition=ready --timeout=1m - kubectl -n apps wait helmrelease/podinfo --for=condition=ready --timeout=1m - - name: Run monitoring tests - # Keep this test in sync with https://fluxcd.io/flux/guides/monitoring/ - env: - KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }} - run: | - ./bin/flux create source git flux-monitoring \ - --interval=30m \ - --url=https://github.com/fluxcd/flux2 \ - --branch=${GITHUB_REF#refs/heads/} - ./bin/flux create kustomization kube-prometheus-stack \ - --interval=1h \ - --prune \ - --source=flux-monitoring \ - --path="./manifests/monitoring/kube-prometheus-stack" \ - --health-check-timeout=5m \ - --wait - ./bin/flux create kustomization monitoring-config \ - --depends-on=kube-prometheus-stack \ - --interval=1h \ - --prune=true \ - --source=flux-monitoring \ - --path="./manifests/monitoring/monitoring-config" \ - --health-check-timeout=1m \ - --wait - kubectl -n flux-system wait kustomization/kube-prometheus-stack --for=condition=ready --timeout=5m - kubectl -n flux-system wait kustomization/monitoring-config --for=condition=ready --timeout=5m - kubectl -n monitoring wait helmrelease/kube-prometheus-stack --for=condition=ready --timeout=1m - - name: Debug failure - if: failure() - env: - KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }} - run: | - kubectl -n flux-system get all - kubectl -n flux-system describe po - kubectl -n flux-system logs deploy/source-controller - kubectl -n flux-system logs deploy/kustomize-controller - - name: Cleanup - if: always() - run: | - kind delete cluster --name ${{ steps.prep.outputs.CLUSTER }} - rm /tmp/${{ steps.prep.outputs.CLUSTER }} diff --git a/.github/workflows/e2e-azure.yaml b/.github/workflows/e2e-azure.yaml index e4fd80abf7..60a599f7ca 100644 --- a/.github/workflows/e2e-azure.yaml +++ b/.github/workflows/e2e-azure.yaml @@ -3,7 +3,7 @@ name: e2e-azure on: workflow_dispatch: schedule: - - cron: '0 6 * * *' + - cron: '0 6 * * *' push: branches: - main @@ -21,48 +21,71 @@ permissions: contents: read jobs: - e2e-amd64-aks: + e2e-aks: runs-on: ubuntu-22.04 - if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' + defaults: + run: + working-directory: ./tests/integration + # This job is currently disabled. Remove the false check when Azure subscription is enabled. + if: false && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' steps: - - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - name: CheckoutD + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: - go-version: 1.20.x - cache-dependency-path: | - **/go.sum - **/go.mod + go-version: 1.22.x + cache-dependency-path: tests/integration/go.sum - name: Setup Flux CLI - run: | - make build - mkdir -p $HOME/.local/bin - mv ./bin/flux $HOME/.local/bin + run: make build + working-directory: ./ - name: Setup SOPS run: | - wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux - chmod +x sops-v3.7.1.linux mkdir -p $HOME/.local/bin - mv sops-v3.7.1.linux $HOME/.local/bin/sops - - name: Setup Terraform - uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2 + wget -O $HOME/.local/bin/sops https://github.com/mozilla/sops/releases/download/v$SOPS_VER/sops-v$SOPS_VER.linux + chmod +x $HOME/.local/bin/sops + env: + SOPS_VER: 3.7.1 + - name: Authenticate to Azure + uses: Azure/login@6b2456866fc08b011acb422a92a4aa20e2c4de32 # v1.4.6 with: - terraform_version: 1.2.8 - terraform_wrapper: false - - name: Setup Azure CLI + creds: '{"clientId":"${{ secrets.AZ_ARM_CLIENT_ID }}","clientSecret":"${{ secrets.AZ_ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZ_ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZ_ARM_TENANT_ID }}"}' + - name: Set dynamic variables in .env run: | - curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash + cat > .env < build/ssh/key + export GITREPO_SSH_PATH=build/ssh/key + touch ./build/ssh/key.pub + echo $GITREPO_SSH_PUB_CONTENTS | base64 -d > ./build/ssh/key.pub + export GITREPO_SSH_PUB_PATH=build/ssh/key.pub + make test-azure + - name: Ensure resource cleanup + if: ${{ always() }} + env: + ARM_CLIENT_ID: ${{ secrets.AZ_ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.AZ_ARM_CLIENT_SECRET }} + ARM_SUBSCRIPTION_ID: ${{ secrets.AZ_ARM_SUBSCRIPTION_ID }} + ARM_TENANT_ID: ${{ secrets.AZ_ARM_TENANT_ID }} + TF_VAR_azuredevops_org: ${{ secrets.TF_VAR_azuredevops_org }} + TF_VAR_azuredevops_pat: ${{ secrets.TF_VAR_azuredevops_pat }} + TF_VAR_location: ${{ vars.TF_VAR_azure_location }} + run: source .env && make destroy-azure diff --git a/.github/workflows/e2e-bootstrap.yaml b/.github/workflows/e2e-bootstrap.yaml index 9827b3fd3a..a22c86edc9 100644 --- a/.github/workflows/e2e-bootstrap.yaml +++ b/.github/workflows/e2e-bootstrap.yaml @@ -17,29 +17,29 @@ jobs: if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: - go-version: 1.20.x + go-version: 1.22.x cache-dependency-path: | **/go.sum **/go.mod - name: Setup Kubernetes - uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0 + uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0 with: - version: v0.20.0 + version: v0.22.0 cluster_name: kind # The versions below should target the newest Kubernetes version # Keep this up-to-date with https://endoflife.date/kubernetes - node_image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72 - kubectl_version: v1.27.3 + node_image: ghcr.io/fluxcd/kindest/node:v1.30.0-amd64 + kubectl_version: v1.30.0 - name: Setup Kustomize uses: fluxcd/pkg/actions/kustomize@main + - name: Setup yq + uses: fluxcd/pkg/actions/yq@main - name: Build - run: | - make cmd/flux/.manifests.done - go build -o /tmp/flux ./cmd/flux + run: make build-dev - name: Set outputs id: vars run: | @@ -51,18 +51,24 @@ jobs: echo "test_repo_name=$TEST_REPO_NAME" >> $GITHUB_OUTPUT - name: bootstrap init run: | - /tmp/flux bootstrap github --manifests ./manifests/install/ \ + ./bin/flux bootstrap github --manifests ./manifests/install/ \ --owner=fluxcd-testing \ + --image-pull-secret=ghcr-auth \ + --registry-creds=fluxcd:$GITHUB_TOKEN \ --repository=${{ steps.vars.outputs.test_repo_name }} \ --branch=main \ --path=test-cluster \ --team=team-z env: GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} + - name: verify image pull secret + run: | + kubectl -n flux-system get secret ghcr-auth | grep dockerconfigjson - name: bootstrap no-op run: | - /tmp/flux bootstrap github --manifests ./manifests/install/ \ + ./bin/flux bootstrap github --manifests ./manifests/install/ \ --owner=fluxcd-testing \ + --image-pull-secret=ghcr-auth \ --repository=${{ steps.vars.outputs.test_repo_name }} \ --branch=main \ --path=test-cluster \ @@ -72,7 +78,7 @@ jobs: - name: bootstrap customize run: | make setup-bootstrap-patch - /tmp/flux bootstrap github --manifests ./manifests/install/ \ + ./bin/flux bootstrap github --manifests ./manifests/install/ \ --owner=fluxcd-testing \ --repository=${{ steps.vars.outputs.test_repo_name }} \ --branch=main \ @@ -87,46 +93,31 @@ jobs: GITHUB_ORG_NAME: fluxcd-testing - name: uninstall run: | - /tmp/flux uninstall -s --keep-namespace + ./bin/flux uninstall -s --keep-namespace kubectl delete ns flux-system --timeout=10m --wait=true - name: test image automation run: | make setup-image-automation - /tmp/flux bootstrap github --manifests ./manifests/install/ \ + ./bin/flux bootstrap github --manifests ./manifests/install/ \ --owner=fluxcd-testing \ --repository=${{ steps.vars.outputs.test_repo_name }} \ --branch=main \ --path=test-cluster \ --read-write-key - /tmp/flux reconcile image repository podinfo - /tmp/flux get images all - - retries=10 - count=0 - ok=false - until ${ok}; do - /tmp/flux get image update flux-system | grep 'commit' && ok=true || ok=false - count=$(($count + 1)) - if [[ ${count} -eq ${retries} ]]; then - echo "No more retries left" - exit 1 - fi - sleep 6 - /tmp/flux reconcile image update flux-system - done + ./bin/flux reconcile image repository podinfo + ./bin/flux reconcile image update flux-system + ./bin/flux get images all + kubectl -n flux-system get -o yaml ImageUpdateAutomation flux-system | \ + yq '.status.lastPushCommit | length > 1' | grep 'true' env: GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} GITHUB_REPO_NAME: ${{ steps.vars.outputs.test_repo_name }} GITHUB_ORG_NAME: fluxcd-testing - name: delete repository if: ${{ always() }} + continue-on-error: true run: | - curl \ - -X DELETE \ - -H "Accept: application/vnd.github.v3+json" \ - -H "Authorization: token ${GITHUB_TOKEN}" \ - --fail --silent \ - https://api.github.com/repos/fluxcd-testing/${{ steps.vars.outputs.test_repo_name }} + gh repo delete fluxcd-testing/${{ steps.vars.outputs.test_repo_name }} --yes env: GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} - name: Debug failure diff --git a/.github/workflows/e2e-gcp.yaml b/.github/workflows/e2e-gcp.yaml new file mode 100644 index 0000000000..8e4801d97a --- /dev/null +++ b/.github/workflows/e2e-gcp.yaml @@ -0,0 +1,102 @@ +name: e2e-gcp + +on: + workflow_dispatch: + schedule: + - cron: '0 6 * * *' + push: + branches: + - main + paths: + - 'tests/**' + - '.github/workflows/e2e-gcp.yaml' + pull_request: + branches: + - main + paths: + - 'tests/**' + - '.github/workflows/e2e-gcp.yaml' + +permissions: + contents: read + +jobs: + e2e-gcp: + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: ./tests/integration + if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' + steps: + - name: Checkout + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - name: Setup Go + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + with: + go-version: 1.22.x + cache-dependency-path: tests/integration/go.sum + - name: Setup Flux CLI + run: make build + working-directory: ./ + - name: Setup SOPS + run: | + mkdir -p $HOME/.local/bin + wget -O $HOME/.local/bin/sops https://github.com/mozilla/sops/releases/download/v$SOPS_VER/sops-v$SOPS_VER.linux + chmod +x $HOME/.local/bin/sops + env: + SOPS_VER: 3.7.1 + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@55bd3a7c6e2ae7cf1877fd1ccb9d54c0503c457c # v2.1.2 + id: 'auth' + with: + credentials_json: '${{ secrets.FLUX2_E2E_GOOGLE_CREDENTIALS }}' + token_format: 'access_token' + - name: Setup gcloud + uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 # v2.1.0 + - name: Setup QEMU + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + - name: Log into us-central1-docker.pkg.dev + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + with: + registry: us-central1-docker.pkg.dev + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + - name: Set dynamic variables in .env + run: | + cat > .env < build/ssh/key + export GITREPO_SSH_PATH=build/ssh/key + touch ./build/ssh/key.pub + echo $GITREPO_SSH_PUB_CONTENTS | base64 -d > ./build/ssh/key.pub + export GITREPO_SSH_PUB_PATH=build/ssh/key.pub + make test-gcp + - name: Ensure resource cleanup + if: ${{ always() }} + env: + TF_VAR_gcp_project_id: ${{ vars.TF_VAR_gcp_project_id }} + TF_VAR_gcp_region: ${{ vars.TF_VAR_gcp_region }} + TF_VAR_gcp_zone: ${{ vars.TF_VAR_gcp_zone }} + TF_VAR_gcp_email: ${{ secrets.TF_VAR_gcp_email }} + TF_VAR_gcp_keyring: ${{ secrets.TF_VAR_gcp_keyring }} + TF_VAR_gcp_crypto_key: ${{ secrets.TF_VAR_gcp_crypto_key }} + run: source .env && make destroy-gcp diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 290a47c9ee..bd32d9e3d1 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -13,7 +13,9 @@ permissions: jobs: e2e-amd64-kubernetes: - runs-on: ubuntu-latest + runs-on: + group: "Default Larger Runners" + labels: ubuntu-latest-16-cores services: registry: image: registry:2 @@ -21,28 +23,28 @@ jobs: - 5000:5000 steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: - go-version: 1.20.x + go-version: 1.22.x cache-dependency-path: | **/go.sum **/go.mod - name: Setup Kubernetes - uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0 + uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0 with: - version: v0.20.0 + version: v0.22.0 cluster_name: kind + wait: 5s config: .github/kind/config.yaml # disable KIND-net - # The versions below should target the newest Kubernetes version + # The versions below should target the oldest supported Kubernetes version # Keep this up-to-date with https://endoflife.date/kubernetes - node_image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72 - kubectl_version: v1.27.3 + node_image: ghcr.io/fluxcd/kindest/node:v1.28.9-amd64 + kubectl_version: v1.28.9 - name: Setup Calico for network policy run: | - kubectl apply -f https://docs.projectcalico.org/v3.25/manifests/calico.yaml - kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true + kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml - name: Setup Kustomize uses: fluxcd/pkg/actions/kustomize@main - name: Run tests @@ -57,44 +59,43 @@ jobs: exit 1 fi - name: Build - run: | - go build -o /tmp/flux ./cmd/flux + run: make build-dev - name: flux check --pre run: | - /tmp/flux check --pre + ./bin/flux check --pre - name: flux install --manifests run: | - /tmp/flux install --manifests ./manifests/install/ + ./bin/flux install --manifests ./manifests/install/ - name: flux create secret run: | - /tmp/flux create secret git git-ssh-test \ + ./bin/flux create secret git git-ssh-test \ --url ssh://git@github.com/stefanprodan/podinfo - /tmp/flux create secret git git-https-test \ + ./bin/flux create secret git git-https-test \ --url https://github.com/stefanprodan/podinfo \ --username=test --password=test - /tmp/flux create secret helm helm-test \ + ./bin/flux create secret helm helm-test \ --username=test --password=test - name: flux create source git run: | - /tmp/flux create source git podinfo \ + ./bin/flux create source git podinfo \ --url https://github.com/stefanprodan/podinfo \ --tag-semver=">=6.3.5" - name: flux create source git export apply run: | - /tmp/flux create source git podinfo-export \ + ./bin/flux create source git podinfo-export \ --url https://github.com/stefanprodan/podinfo \ --tag-semver=">=6.3.5" \ --export | kubectl apply -f - - /tmp/flux delete source git podinfo-export --silent + ./bin/flux delete source git podinfo-export --silent - name: flux get sources git run: | - /tmp/flux get sources git + ./bin/flux get sources git - name: flux get sources git --all-namespaces run: | - /tmp/flux get sources git --all-namespaces + ./bin/flux get sources git --all-namespaces - name: flux create kustomization run: | - /tmp/flux create kustomization podinfo \ + ./bin/flux create kustomization podinfo \ --source=podinfo \ --path="./deploy/overlays/dev" \ --prune=true \ @@ -104,89 +105,89 @@ jobs: --health-check-timeout=3m - name: flux trace run: | - /tmp/flux trace frontend \ + ./bin/flux trace frontend \ --kind=deployment \ --api-version=apps/v1 \ --namespace=dev - name: flux reconcile kustomization --with-source run: | - /tmp/flux reconcile kustomization podinfo --with-source + ./bin/flux reconcile kustomization podinfo --with-source - name: flux get kustomizations run: | - /tmp/flux get kustomizations + ./bin/flux get kustomizations - name: flux get kustomizations --all-namespaces run: | - /tmp/flux get kustomizations --all-namespaces + ./bin/flux get kustomizations --all-namespaces - name: flux suspend kustomization run: | - /tmp/flux suspend kustomization podinfo + ./bin/flux suspend kustomization podinfo - name: flux resume kustomization run: | - /tmp/flux resume kustomization podinfo + ./bin/flux resume kustomization podinfo - name: flux export run: | - /tmp/flux export source git --all - /tmp/flux export kustomization --all + ./bin/flux export source git --all + ./bin/flux export kustomization --all - name: flux delete kustomization run: | - /tmp/flux delete kustomization podinfo --silent + ./bin/flux delete kustomization podinfo --silent - name: flux create source helm run: | - /tmp/flux create source helm podinfo \ + ./bin/flux create source helm podinfo \ --url https://stefanprodan.github.io/podinfo - name: flux create helmrelease --source=HelmRepository/podinfo run: | - /tmp/flux create hr podinfo-helm \ + ./bin/flux create hr podinfo-helm \ --target-namespace=default \ --source=HelmRepository/podinfo.flux-system \ --chart=podinfo \ --chart-version=">6.0.0 <7.0.0" - name: flux create helmrelease --source=GitRepository/podinfo run: | - /tmp/flux create hr podinfo-git \ + ./bin/flux create hr podinfo-git \ --target-namespace=default \ --source=GitRepository/podinfo \ --chart=./charts/podinfo - name: flux reconcile helmrelease --with-source run: | - /tmp/flux reconcile helmrelease podinfo-git --with-source + ./bin/flux reconcile helmrelease podinfo-git --with-source - name: flux get helmreleases run: | - /tmp/flux get helmreleases + ./bin/flux get helmreleases - name: flux get helmreleases --all-namespaces run: | - /tmp/flux get helmreleases --all-namespaces + ./bin/flux get helmreleases --all-namespaces - name: flux export helmrelease run: | - /tmp/flux export hr --all + ./bin/flux export hr --all - name: flux delete helmrelease podinfo-helm run: | - /tmp/flux delete hr podinfo-helm --silent + ./bin/flux delete hr podinfo-helm --silent - name: flux delete helmrelease podinfo-git run: | - /tmp/flux delete hr podinfo-git --silent + ./bin/flux delete hr podinfo-git --silent - name: flux delete source helm run: | - /tmp/flux delete source helm podinfo --silent + ./bin/flux delete source helm podinfo --silent - name: flux delete source git run: | - /tmp/flux delete source git podinfo --silent + ./bin/flux delete source git podinfo --silent - name: flux oci artifacts run: | - /tmp/flux push artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \ + ./bin/flux push artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \ --path="./manifests" \ --source="${{ github.repositoryUrl }}" \ --revision="${{ github.ref }}@sha1:${{ github.sha }}" - /tmp/flux tag artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \ + ./bin/flux tag artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \ --tag latest - /tmp/flux list artifacts oci://localhost:5000/fluxcd/flux + ./bin/flux list artifacts oci://localhost:5000/fluxcd/flux - name: flux oci repositories run: | - /tmp/flux create source oci podinfo-oci \ + ./bin/flux create source oci podinfo-oci \ --url oci://ghcr.io/stefanprodan/manifests/podinfo \ --tag-semver 6.3.x \ --interval 10m - /tmp/flux create kustomization podinfo-oci \ + ./bin/flux create kustomization podinfo-oci \ --source=OCIRepository/podinfo-oci \ --path="./" \ --prune=true \ @@ -194,31 +195,31 @@ jobs: --target-namespace=default \ --wait=true \ --health-check-timeout=3m - /tmp/flux reconcile source oci podinfo-oci - /tmp/flux suspend source oci podinfo-oci - /tmp/flux get sources oci - /tmp/flux resume source oci podinfo-oci - /tmp/flux export source oci podinfo-oci - /tmp/flux delete ks podinfo-oci --silent - /tmp/flux delete source oci podinfo-oci --silent + ./bin/flux reconcile source oci podinfo-oci + ./bin/flux suspend source oci podinfo-oci + ./bin/flux get sources oci + ./bin/flux resume source oci podinfo-oci + ./bin/flux export source oci podinfo-oci + ./bin/flux delete ks podinfo-oci --silent + ./bin/flux delete source oci podinfo-oci --silent - name: flux create tenant run: | - /tmp/flux create tenant dev-team --with-namespace=apps - /tmp/flux -n apps create source helm podinfo \ + ./bin/flux create tenant dev-team --with-namespace=apps + ./bin/flux -n apps create source helm podinfo \ --url https://stefanprodan.github.io/podinfo - /tmp/flux -n apps create hr podinfo-helm \ + ./bin/flux -n apps create hr podinfo-helm \ --source=HelmRepository/podinfo \ --chart=podinfo \ --chart-version="6.3.x" \ --service-account=dev-team - name: flux2-kustomize-helm-example run: | - /tmp/flux create source git flux-system \ + ./bin/flux create source git flux-system \ --url=https://github.com/fluxcd/flux2-kustomize-helm-example \ --branch=main \ --ignore-paths="./clusters/**/flux-system/" \ --recurse-submodules - /tmp/flux create kustomization flux-system \ + ./bin/flux create kustomization flux-system \ --source=flux-system \ --path=./clusters/staging kubectl -n flux-system wait kustomization/infra-controllers --for=condition=ready --timeout=5m @@ -226,13 +227,23 @@ jobs: kubectl -n podinfo wait helmrelease/podinfo --for=condition=ready --timeout=5m - name: flux tree run: | - /tmp/flux tree kustomization flux-system | grep Service/podinfo + ./bin/flux tree kustomization flux-system | grep Service/podinfo + - name: flux events + run: | + ./bin/flux -n flux-system events --for Kustomization/apps | grep 'HelmRelease/podinfo' + ./bin/flux -n podinfo events --for HelmRelease/podinfo | grep 'podinfo.v1' + - name: flux stats + run: | + ./bin/flux stats -A - name: flux check run: | - /tmp/flux check + ./bin/flux check + - name: flux version + run: | + ./bin/flux version - name: flux uninstall run: | - /tmp/flux uninstall --silent + ./bin/flux uninstall --silent - name: Debug failure if: failure() run: | diff --git a/.github/workflows/ossf.yaml b/.github/workflows/ossf.yaml index 9051b6417c..7c1b6eb89d 100644 --- a/.github/workflows/ossf.yaml +++ b/.github/workflows/ossf.yaml @@ -19,16 +19,16 @@ jobs: actions: read contents: read steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Run analysis - uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0 + uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 with: results_file: results.sarif results_format: sarif repo_token: ${{ secrets.GITHUB_TOKEN }} publish_results: true - name: Upload artifact - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 499bb12051..30adf1373e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -20,33 +20,33 @@ jobs: packages: write # needed for ghcr access steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Unshallow run: git fetch --prune --unshallow - name: Setup Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: - go-version: 1.20.x + go-version: 1.22.x cache: false - name: Setup QEMU - uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Setup Docker Buildx id: buildx - uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1 # v2.9.1 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Setup Syft - uses: anchore/sbom-action/download-syft@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1 # v0.14.3 + uses: anchore/sbom-action/download-syft@7ccf588e3cf3cc2611714c2eeae48550fbc17552 # v0.15.11 - name: Setup Cosign - uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 # v3.1.1 + uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0 - name: Setup Kustomize uses: fluxcd/pkg/actions/kustomize@main - name: Login to GitHub Container Registry - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: registry: ghcr.io username: fluxcdbot password: ${{ secrets.GHCR_TOKEN }} - name: Login to Docker Hub - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: username: fluxcdbot password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }} @@ -79,7 +79,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Run GoReleaser id: run-goreleaser - uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 # v4.3.0 + uses: goreleaser/goreleaser-action@5742e2a039330cbb23ebf35f046f814d4c6ff811 # v5.1.0 with: version: latest args: release --release-notes=output/notes.md --skip-validate @@ -110,7 +110,7 @@ jobs: id-token: write packages: write steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup Kustomize uses: fluxcd/pkg/actions/kustomize@main - name: Setup Flux CLI @@ -121,13 +121,13 @@ jobs: VERSION=$(flux version --client | awk '{ print $NF }') echo "version=${VERSION}" >> $GITHUB_OUTPUT - name: Login to GHCR - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: registry: ghcr.io username: fluxcdbot password: ${{ secrets.GHCR_TOKEN }} - name: Login to DockerHub - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: username: fluxcdbot password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }} @@ -155,7 +155,7 @@ jobs: --path="./flux-system" \ --source=${{ github.repositoryUrl }} \ --revision="${{ github.ref_name }}@sha1:${{ github.sha }}" - - uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 # v3.1.1 + - uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0 - name: Sign manifests env: COSIGN_EXPERIMENTAL: 1 @@ -176,7 +176,7 @@ jobs: actions: read # for detecting the Github Actions environment. id-token: write # for creating OIDC tokens for signing. contents: write # for uploading attestations to GitHub releases. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.7.0 + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 with: provenance-name: "provenance.intoto.jsonl" base64-subjects: "${{ needs.release-flux-cli.outputs.hashes }}" @@ -188,7 +188,7 @@ jobs: actions: read # for detecting the Github Actions environment. id-token: write # for creating OIDC tokens for signing. packages: write # for uploading attestations. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.7.0 + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0 with: image: ${{ needs.release-flux-cli.outputs.image_url }} digest: ${{ needs.release-flux-cli.outputs.image_digest }} @@ -202,7 +202,7 @@ jobs: actions: read # for detecting the Github Actions environment. id-token: write # for creating OIDC tokens for signing. packages: write # for uploading attestations. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.7.0 + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0 with: image: ghcr.io/${{ needs.release-flux-cli.outputs.image_url }} digest: ${{ needs.release-flux-cli.outputs.image_digest }} diff --git a/.github/workflows/scan.yaml b/.github/workflows/scan.yaml index ca68607f84..66ba1add71 100644 --- a/.github/workflows/scan.yaml +++ b/.github/workflows/scan.yaml @@ -17,9 +17,9 @@ jobs: runs-on: ubuntu-latest if: github.actor != 'dependabot[bot]' steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Run FOSSA scan and upload build data - uses: fossa-contrib/fossa-action@6728dc6fe9a068c648d080c33829ffbe56565023 # v2.0.0 + uses: fossa-contrib/fossa-action@cdc5065bcdee31a32e47d4585df72d66e8e941c2 # v3.0.0 with: # FOSSA Push-Only API Token fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de @@ -31,13 +31,13 @@ jobs: security-events: write if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup Kustomize uses: fluxcd/pkg/actions/kustomize@main - name: Setup Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: - go-version: 1.20.x + go-version-file: 'go.mod' cache-dependency-path: | **/go.sum **/go.mod @@ -49,10 +49,11 @@ jobs: - name: Run Snyk to check for vulnerabilities continue-on-error: true run: | - snyk test --sarif-file-output=snyk.sarif + snyk test --all-projects --sarif-file-output=snyk.sarif env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - name: Upload result to GitHub Code Scanning + continue-on-error: true uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4 with: sarif_file: snyk.sarif @@ -64,11 +65,11 @@ jobs: if: github.actor != 'dependabot[bot]' steps: - name: Checkout repository - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: - go-version: 1.20.x + go-version-file: 'go.mod' cache-dependency-path: | **/go.sum **/go.mod @@ -76,6 +77,9 @@ jobs: uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4 with: languages: go + # xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # xref: https://codeql.github.com/codeql-query-help/go/ + queries: security-and-quality - name: Autobuild uses: github/codeql-action/autobuild@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4 - name: Perform CodeQL Analysis diff --git a/.github/workflows/sync-labels.yaml b/.github/workflows/sync-labels.yaml index f0688a426c..5a84a47287 100644 --- a/.github/workflows/sync-labels.yaml +++ b/.github/workflows/sync-labels.yaml @@ -17,8 +17,8 @@ jobs: permissions: issues: write steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - uses: EndBug/label-sync@da00f2c11fdb78e4fae44adac2fdd713778ea3e8 # v2.3.2 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3 with: # Configuration file config-file: | diff --git a/.github/workflows/update.yaml b/.github/workflows/update.yaml index 67996a8aa0..b7de53c04d 100644 --- a/.github/workflows/update.yaml +++ b/.github/workflows/update.yaml @@ -18,11 +18,11 @@ jobs: pull-requests: write steps: - name: Check out code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: - go-version: 1.20.x + go-version: 1.22.x cache-dependency-path: | **/go.sum **/go.mod @@ -84,7 +84,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2 + uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6.0.5 with: token: ${{ secrets.BOT_GITHUB_TOKEN }} commit-message: | diff --git a/Dockerfile b/Dockerfile index 0a4bcbf2af..c918f4b064 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,15 @@ -FROM alpine:3.18 as builder +FROM alpine:3.19 as builder RUN apk add --no-cache ca-certificates curl ARG ARCH=linux/amd64 -ARG KUBECTL_VER=1.27.3 +ARG KUBECTL_VER=1.30.0 RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \ -o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \ kubectl version --client=true -FROM alpine:3.18 as flux-cli +FROM alpine:3.19 as flux-cli RUN apk add --no-cache ca-certificates diff --git a/Makefile b/Makefile index 563cd0b170..787d369f2f 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,8 @@ rwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2 all: test build tidy: - go mod tidy -compat=1.20 - cd tests/azure && go mod tidy -compat=1.20 + go mod tidy -compat=1.22 + cd tests/integration && go mod tidy -compat=1.22 fmt: go fmt ./... diff --git a/README.md b/README.md index b19f9b3f0c..7eb5deeac2 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2/badge)](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2) [![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) [![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/flux2)](https://artifacthub.io/packages/helm/fluxcd-community/flux2) +[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://fluxcd.io/flux/security/slsa-assessment) Flux is a tool for keeping Kubernetes clusters in sync with sources of configuration (like Git repositories and OCI artifacts), @@ -20,7 +21,7 @@ Flux v2 is constructed with the [GitOps Toolkit](#gitops-toolkit), a set of composable APIs and specialized tools for building Continuous Delivery on top of Kubernetes. -Flux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) project, used in +Flux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) graduated project, used in production by various [organisations](https://fluxcd.io/adopters) and [cloud providers](https://fluxcd.io/ecosystem). ## Quickstart and documentation @@ -32,7 +33,7 @@ For more comprehensive documentation, see the following guides: - [Ways of structuring your repositories](https://fluxcd.io/flux/guides/repository-structure/) - [Manage Helm Releases](https://fluxcd.io/flux/guides/helmreleases/) - [Automate image updates to Git](https://fluxcd.io/flux/guides/image-update/) -- [Manage Kubernetes secrets with Mozilla SOPS](https://fluxcd.io/flux/guides/mozilla-sops/) +- [Manage Kubernetes secrets with Flux and SOPS](https://fluxcd.io/flux/guides/mozilla-sops/) If you need help, please refer to our **[Support page](https://fluxcd.io/support/)**. @@ -43,7 +44,7 @@ runtime for Flux v2. The APIs comprise Kubernetes custom resources, which can be created and updated by a cluster user, or by other automation tooling. -![overview](https://fluxcd.io/img/diagrams/gitops-toolkit.png) +![overview](https://raw.githubusercontent.com/fluxcd/flux2/main/docs/diagrams/fluxcd-controllers.png) You can use the toolkit to extend Flux, or to build your own systems for continuous delivery -- see [the developer @@ -58,18 +59,18 @@ guides](https://fluxcd.io/flux/gitops-toolkit/source-watcher/). - [HelmChart CRD](https://fluxcd.io/flux/components/source/helmcharts/) - [Bucket CRD](https://fluxcd.io/flux/components/source/buckets/) - [Kustomize Controller](https://fluxcd.io/flux/components/kustomize/) - - [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomization/) + - [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomizations/) - [Helm Controller](https://fluxcd.io/flux/components/helm/) - [HelmRelease CRD](https://fluxcd.io/flux/components/helm/helmreleases/) - [Notification Controller](https://fluxcd.io/flux/components/notification/) - - [Provider CRD](https://fluxcd.io/flux/components/notification/provider/) - - [Alert CRD](https://fluxcd.io/flux/components/notification/alert/) - - [Receiver CRD](https://fluxcd.io/flux/components/notification/receiver/) + - [Provider CRD](https://fluxcd.io/flux/components/notification/providers/) + - [Alert CRD](https://fluxcd.io/flux/components/notification/alerts/) + - [Receiver CRD](https://fluxcd.io/flux/components/notification/receivers/) - [Image Automation Controllers](https://fluxcd.io/flux/components/image/) - [ImageRepository CRD](https://fluxcd.io/flux/components/image/imagerepositories/) - [ImagePolicy CRD](https://fluxcd.io/flux/components/image/imagepolicies/) - [ImageUpdateAutomation CRD](https://fluxcd.io/flux/components/image/imageupdateautomations/) - + ## Community Need help or want to contribute? Please see the links below. The Flux project is always looking for diff --git a/action/README.md b/action/README.md index 052277ac7c..d511dc9cc0 100644 --- a/action/README.md +++ b/action/README.md @@ -18,5 +18,5 @@ The Flux GitHub Action can be used to automate various tasks in CI, such as: - [Push Kubernetes manifests to container registries](https://fluxcd.io/flux/flux-gh-action/#push-kubernetes-manifests-to-container-registries) - [Run end-to-end testing with Flux and Kubernetes Kind](https://fluxcd.io/flux/flux-gh-action/#end-to-end-testing) -For more information, please see the [Flux GitHub Action documentation](/flux/flux-gh-action.md). +For more information, please see the [Flux GitHub Action documentation](https://fluxcd.io/flux/flux-gh-action/). diff --git a/cmd/flux/alert.go b/cmd/flux/alert.go index 366a9a4af0..16310e3a82 100644 --- a/cmd/flux/alert.go +++ b/cmd/flux/alert.go @@ -19,7 +19,7 @@ package main import ( "sigs.k8s.io/controller-runtime/pkg/client" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" ) // notificationv1.Alert diff --git a/cmd/flux/alert_provider.go b/cmd/flux/alert_provider.go index 3ee071cef1..c370a5c038 100644 --- a/cmd/flux/alert_provider.go +++ b/cmd/flux/alert_provider.go @@ -19,7 +19,7 @@ package main import ( "sigs.k8s.io/controller-runtime/pkg/client" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" ) // notificationv1.Provider diff --git a/cmd/flux/bootstrap.go b/cmd/flux/bootstrap.go index 2441551df7..d23540e993 100644 --- a/cmd/flux/bootstrap.go +++ b/cmd/flux/bootstrap.go @@ -17,11 +17,16 @@ limitations under the License. package main import ( + "context" "crypto/elliptic" "fmt" "strings" + "github.com/fluxcd/pkg/git" + "github.com/manifoldco/promptui" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/utils" @@ -48,17 +53,19 @@ type bootstrapFlags struct { extraComponents []string requiredComponents []string - registry string - imagePullSecret string + registry string + registryCredential string + imagePullSecret string - secretName string - tokenAuth bool - keyAlgorithm flags.PublicKeyAlgorithm - keyRSABits flags.RSAKeyBits - keyECDSACurve flags.ECDSACurve - sshHostname string - caFile string - privateKeyFile string + secretName string + tokenAuth bool + keyAlgorithm flags.PublicKeyAlgorithm + keyRSABits flags.RSAKeyBits + keyECDSACurve flags.ECDSACurve + sshHostname string + caFile string + privateKeyFile string + sshHostKeyAlgorithms []string watchAllNamespaces bool networkPolicy bool @@ -72,6 +79,8 @@ type bootstrapFlags struct { gpgPassphrase string gpgKeyID string + force bool + commitMessageAppendix string } @@ -92,6 +101,8 @@ func init() { bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd", "container registry where the Flux controller images are published") + bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registryCredential, "registry-creds", "", + "container registry credentials in the format 'user:password', requires --image-pull-secret to be set") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.imagePullSecret, "image-pull-secret", "", "Kubernetes secret name used for pulling the controller images from a private registry") @@ -115,6 +126,7 @@ func init() { bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.secretName, "secret-name", rootArgs.defaults.Namespace, "name of the secret the sync credentials can be found in or stored to") bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyAlgorithm, "ssh-key-algorithm", bootstrapArgs.keyAlgorithm.Description()) bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyRSABits, "ssh-rsa-bits", bootstrapArgs.keyRSABits.Description()) + bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.sshHostKeyAlgorithms, "ssh-hostkey-algos", nil, "list of host key algorithms to be used by the CLI for SSH connections") bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyECDSACurve, "ssh-ecdsa-curve", bootstrapArgs.keyECDSACurve.Description()) bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.sshHostname, "ssh-hostname", "", "SSH hostname, to be used when the SSH host differs from the HTTPS one") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates") @@ -129,6 +141,7 @@ func init() { bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'") + bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.force, "force", false, "override existing Flux installation if it's managed by a different tool such as Helm") bootstrapCmd.PersistentFlags().MarkHidden("manifests") rootCmd.AddCommand(bootstrapCmd) @@ -174,6 +187,18 @@ func bootstrapValidate() error { return err } + if bootstrapArgs.registryCredential != "" && bootstrapArgs.imagePullSecret == "" { + return fmt.Errorf("--registry-creds requires --image-pull-secret to be set") + } + + if bootstrapArgs.registryCredential != "" && len(strings.Split(bootstrapArgs.registryCredential, ":")) != 2 { + return fmt.Errorf("invalid --registry-creds format, expected 'user:password'") + } + + if len(bootstrapArgs.sshHostKeyAlgorithms) > 0 { + git.HostKeyAlgos = bootstrapArgs.sshHostKeyAlgorithms + } + return nil } @@ -188,3 +213,27 @@ func mapTeamSlice(s []string, defaultPermission string) map[string]string { return m } + +// confirmBootstrap gets a confirmation for running bootstrap over an existing Flux installation. +// It returns a nil error if Flux is not installed or the user confirms overriding an existing installation +func confirmBootstrap(ctx context.Context, kubeClient client.Client) error { + installed := true + info, err := getFluxClusterInfo(ctx, kubeClient) + if err != nil { + if !errors.IsNotFound(err) { + return fmt.Errorf("cluster info unavailable: %w", err) + } + installed = false + } + + if installed { + err = confirmFluxInstallOverride(info) + if err != nil { + if err == promptui.ErrAbort { + return fmt.Errorf("bootstrap cancelled") + } + return err + } + } + return nil +} diff --git a/cmd/flux/bootstrap_bitbucket_server.go b/cmd/flux/bootstrap_bitbucket_server.go index 40eccca88c..c26515f62c 100644 --- a/cmd/flux/bootstrap_bitbucket_server.go +++ b/cmd/flux/bootstrap_bitbucket_server.go @@ -56,7 +56,7 @@ the bootstrap command will perform an upgrade if needed.`, # Run bootstrap for a public repository on a personal account flux bootstrap bitbucket-server --owner= --repository= --private=false --personal --hostname= --token-auth --path=clusters/my-cluster - # Run bootstrap for a an existing repository with a branch named main + # Run bootstrap for an existing repository with a branch named main flux bootstrap bitbucket-server --owner= --username= --repository= --branch=main --hostname= --token-auth --path=clusters/my-cluster`, RunE: bootstrapBServerCmdRun, } @@ -124,6 +124,13 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error { return err } + if !bootstrapArgs.force { + err = confirmBootstrap(ctx, kubeClient) + if err != nil { + return err + } + } + // Manifest base if ver, err := getVersion(bootstrapArgs.version); err != nil { return err @@ -189,6 +196,7 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error { Namespace: *kubeconfigArgs.Namespace, Components: bootstrapComponents(), Registry: bootstrapArgs.registry, + RegistryCredential: bootstrapArgs.registryCredential, ImagePullSecret: bootstrapArgs.imagePullSecret, WatchAllNamespaces: bootstrapArgs.watchAllNamespaces, NetworkPolicy: bootstrapArgs.networkPolicy, diff --git a/cmd/flux/bootstrap_git.go b/cmd/flux/bootstrap_git.go index bd9dc80aa2..6686dcc1cc 100644 --- a/cmd/flux/bootstrap_git.go +++ b/cmd/flux/bootstrap_git.go @@ -28,6 +28,9 @@ import ( "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" + "github.com/fluxcd/pkg/git" + "github.com/fluxcd/pkg/git/gogit" + "github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/pkg/bootstrap" @@ -35,8 +38,6 @@ import ( "github.com/fluxcd/flux2/v2/pkg/manifestgen/install" "github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/v2/pkg/manifestgen/sync" - "github.com/fluxcd/pkg/git" - "github.com/fluxcd/pkg/git/gogit" ) var bootstrapGitCmd = &cobra.Command{ @@ -65,7 +66,10 @@ command will perform an upgrade if needed.`, flux bootstrap git --url=ssh://@git-codecommit..amazonaws.com/v1/repos/ --private-key-file= --password= --path=clusters/my-cluster # Run bootstrap for a Git repository on Azure Devops - flux bootstrap git --url=ssh://git@ssh.dev.azure.com/v3/// --ssh-key-algorithm=rsa --ssh-rsa-bits=4096 --path=clusters/my-cluster + flux bootstrap git --url=ssh://git@ssh.dev.azure.com/v3/// --private-key-file= --ssh-hostkey-algos=rsa-sha2-512,rsa-sha2-256 --path=clusters/my-cluster + + # Run bootstrap for a Git repository on Oracle VBS + flux bootstrap git --url=https://repository_url.git --with-bearer-token=true --password= --path=clusters/my-cluster `, RunE: bootstrapGitCmdRun, } @@ -78,6 +82,7 @@ type gitFlags struct { password string silent bool insecureHttpAllowed bool + withBearerToken bool } const ( @@ -94,11 +99,16 @@ func init() { bootstrapGitCmd.Flags().StringVarP(&gitArgs.password, "password", "p", "", "basic authentication password") bootstrapGitCmd.Flags().BoolVarP(&gitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation") bootstrapGitCmd.Flags().BoolVar(&gitArgs.insecureHttpAllowed, "allow-insecure-http", false, "allows insecure HTTP connections") + bootstrapGitCmd.Flags().BoolVar(&gitArgs.withBearerToken, "with-bearer-token", false, "use password as bearer token for Authorization header") bootstrapCmd.AddCommand(bootstrapGitCmd) } func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error { + if gitArgs.withBearerToken { + bootstrapArgs.tokenAuth = true + } + gitPassword := os.Getenv(gitPasswordEnvVar) if gitPassword != "" && gitArgs.password == "" { gitArgs.password = gitPassword @@ -146,6 +156,13 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error { return err } + if !bootstrapArgs.force { + err = confirmBootstrap(ctx, kubeClient) + if err != nil { + return err + } + } + // Manifest base if ver, err := getVersion(bootstrapArgs.version); err != nil { return err @@ -194,6 +211,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error { Namespace: *kubeconfigArgs.Namespace, Components: bootstrapComponents(), Registry: bootstrapArgs.registry, + RegistryCredential: bootstrapArgs.registryCredential, ImagePullSecret: bootstrapArgs.imagePullSecret, WatchAllNamespaces: bootstrapArgs.watchAllNamespaces, NetworkPolicy: bootstrapArgs.networkPolicy, @@ -216,9 +234,15 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error { TargetPath: gitArgs.path.String(), ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile, } + if bootstrapArgs.tokenAuth { - secretOpts.Username = gitArgs.username - secretOpts.Password = gitArgs.password + if gitArgs.withBearerToken { + secretOpts.BearerToken = gitArgs.password + } else { + secretOpts.Username = gitArgs.username + secretOpts.Password = gitArgs.password + } + secretOpts.CAFile = caBundle // Remove port of the given host when not syncing over HTTP/S to not assume port for protocol @@ -311,18 +335,28 @@ func getAuthOpts(u *url.URL, caBundle []byte) (*git.AuthOptions, error) { if !gitArgs.insecureHttpAllowed { return nil, fmt.Errorf("scheme http is insecure, pass --allow-insecure-http=true to allow it") } - return &git.AuthOptions{ + httpAuth := git.AuthOptions{ Transport: git.HTTP, - Username: gitArgs.username, - Password: gitArgs.password, - }, nil + } + if gitArgs.withBearerToken { + httpAuth.BearerToken = gitArgs.password + } else { + httpAuth.Username = gitArgs.username + httpAuth.Password = gitArgs.password + } + return &httpAuth, nil case "https": - return &git.AuthOptions{ + httpsAuth := git.AuthOptions{ Transport: git.HTTPS, - Username: gitArgs.username, - Password: gitArgs.password, CAFile: caBundle, - }, nil + } + if gitArgs.withBearerToken { + httpsAuth.BearerToken = gitArgs.password + } else { + httpsAuth.Username = gitArgs.username + httpsAuth.Password = gitArgs.password + } + return &httpsAuth, nil case "ssh": authOpts := &git.AuthOptions{ Transport: git.SSH, diff --git a/cmd/flux/bootstrap_gitea.go b/cmd/flux/bootstrap_gitea.go new file mode 100644 index 0000000000..48b18b0ed7 --- /dev/null +++ b/cmd/flux/bootstrap_gitea.go @@ -0,0 +1,276 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/fluxcd/pkg/git" + "github.com/fluxcd/pkg/git/gogit" + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/flags" + "github.com/fluxcd/flux2/v2/internal/utils" + "github.com/fluxcd/flux2/v2/pkg/bootstrap" + "github.com/fluxcd/flux2/v2/pkg/bootstrap/provider" + "github.com/fluxcd/flux2/v2/pkg/manifestgen" + "github.com/fluxcd/flux2/v2/pkg/manifestgen/install" + "github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" + "github.com/fluxcd/flux2/v2/pkg/manifestgen/sync" +) + +var bootstrapGiteaCmd = &cobra.Command{ + Use: "gitea", + Short: "Deploy Flux on a cluster connected to a Gitea repository", + Long: `The bootstrap gitea command creates the Gitea repository if it doesn't exists and +commits the Flux manifests to the specified branch. +Then it configures the target cluster to synchronize with that repository. +If the Flux components are present on the cluster, +the bootstrap command will perform an upgrade if needed.`, + Example: ` # Create a Gitea personal access token and export it as an env var + export GITEA_TOKEN= + + # Run bootstrap for a private repository owned by a Gitea organization + flux bootstrap gitea --owner= --repository= --path=clusters/my-cluster + + # Run bootstrap for a private repository and assign organization teams to it + flux bootstrap gitea --owner= --repository= --team= --team= --path=clusters/my-cluster + + # Run bootstrap for a private repository and assign organization teams with their access level(e.g maintain, admin) to it + flux bootstrap gitea --owner= --repository= --team=: --path=clusters/my-cluster + + # Run bootstrap for a public repository on a personal account + flux bootstrap gitea --owner= --repository= --private=false --personal=true --path=clusters/my-cluster + + # Run bootstrap for a private repository hosted on Gitea Enterprise using SSH auth + flux bootstrap gitea --owner= --repository= --hostname= --ssh-hostname= --path=clusters/my-cluster + + # Run bootstrap for a private repository hosted on Gitea Enterprise using HTTPS auth + flux bootstrap gitea --owner= --repository= --hostname= --token-auth --path=clusters/my-cluster + + # Run bootstrap for an existing repository with a branch named main + flux bootstrap gitea --owner= --repository= --branch=main --path=clusters/my-cluster`, + RunE: bootstrapGiteaCmdRun, +} + +type giteaFlags struct { + owner string + repository string + interval time.Duration + personal bool + private bool + hostname string + path flags.SafeRelativePath + teams []string + readWriteKey bool + reconcile bool +} + +const ( + gtDefaultPermission = "maintain" + gtDefaultDomain = "gitea.com" + gtTokenEnvVar = "GITEA_TOKEN" +) + +var giteaArgs giteaFlags + +func init() { + bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.owner, "owner", "", "Gitea user or organization name") + bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.repository, "repository", "", "Gitea repository name") + bootstrapGiteaCmd.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)") + bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.personal, "personal", false, "if true, the owner is assumed to be a Gitea user; otherwise an org") + bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.private, "private", true, "if true, the repository is setup or configured as private") + bootstrapGiteaCmd.Flags().DurationVar(&giteaArgs.interval, "interval", time.Minute, "sync interval") + bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.hostname, "hostname", gtDefaultDomain, "Gitea hostname") + bootstrapGiteaCmd.Flags().Var(&giteaArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path") + bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions") + bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists") + + bootstrapCmd.AddCommand(bootstrapGiteaCmd) +} + +func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error { + gtToken := os.Getenv(gtTokenEnvVar) + if gtToken == "" { + var err error + gtToken, err = readPasswordFromStdin("Please enter your Gitea personal access token (PAT): ") + if err != nil { + return fmt.Errorf("could not read token: %w", err) + } + } + + if err := bootstrapValidate(); err != nil { + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) + if err != nil { + return err + } + + // Manifest base + if ver, err := getVersion(bootstrapArgs.version); err != nil { + return err + } else { + bootstrapArgs.version = ver + } + manifestsBase, err := buildEmbeddedManifestBase() + if err != nil { + return err + } + defer os.RemoveAll(manifestsBase) + + var caBundle []byte + if bootstrapArgs.caFile != "" { + var err error + caBundle, err = os.ReadFile(bootstrapArgs.caFile) + if err != nil { + return fmt.Errorf("unable to read TLS CA file: %w", err) + } + } + // Build Gitea provider + providerCfg := provider.Config{ + Provider: provider.GitProviderGitea, + Hostname: giteaArgs.hostname, + Token: gtToken, + CaBundle: caBundle, + } + providerClient, err := provider.BuildGitProvider(providerCfg) + if err != nil { + return err + } + + tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-") + if err != nil { + return fmt.Errorf("failed to create temporary working dir: %w", err) + } + defer os.RemoveAll(tmpDir) + + clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()} + gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{ + Transport: git.HTTPS, + Username: giteaArgs.owner, + Password: gtToken, + CAFile: caBundle, + }, clientOpts...) + if err != nil { + return fmt.Errorf("failed to create a Git client: %w", err) + } + + // Install manifest config + installOptions := install.Options{ + BaseURL: rootArgs.defaults.BaseURL, + Version: bootstrapArgs.version, + Namespace: *kubeconfigArgs.Namespace, + Components: bootstrapComponents(), + Registry: bootstrapArgs.registry, + RegistryCredential: bootstrapArgs.registryCredential, + ImagePullSecret: bootstrapArgs.imagePullSecret, + WatchAllNamespaces: bootstrapArgs.watchAllNamespaces, + NetworkPolicy: bootstrapArgs.networkPolicy, + LogLevel: bootstrapArgs.logLevel.String(), + NotificationController: rootArgs.defaults.NotificationController, + ManifestFile: rootArgs.defaults.ManifestFile, + Timeout: rootArgs.timeout, + TargetPath: giteaArgs.path.ToSlash(), + ClusterDomain: bootstrapArgs.clusterDomain, + TolerationKeys: bootstrapArgs.tolerationKeys, + } + if customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != "" { + installOptions.BaseURL = customBaseURL + } + + // Source generation and secret config + secretOpts := sourcesecret.Options{ + Name: bootstrapArgs.secretName, + Namespace: *kubeconfigArgs.Namespace, + TargetPath: giteaArgs.path.ToSlash(), + ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile, + } + if bootstrapArgs.tokenAuth { + secretOpts.Username = "git" + secretOpts.Password = gtToken + secretOpts.CAFile = caBundle + } else { + secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm) + secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits) + secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve + + secretOpts.SSHHostname = giteaArgs.hostname + if bootstrapArgs.sshHostname != "" { + secretOpts.SSHHostname = bootstrapArgs.sshHostname + } + } + + // Sync manifest config + syncOpts := sync.Options{ + Interval: giteaArgs.interval, + Name: *kubeconfigArgs.Namespace, + Namespace: *kubeconfigArgs.Namespace, + Branch: bootstrapArgs.branch, + Secret: bootstrapArgs.secretName, + TargetPath: giteaArgs.path.ToSlash(), + ManifestFile: sync.MakeDefaultOptions().ManifestFile, + RecurseSubmodules: bootstrapArgs.recurseSubmodules, + } + + entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath) + if err != nil { + return err + } + + // Bootstrap config + bootstrapOpts := []bootstrap.GitProviderOption{ + bootstrap.WithProviderRepository(giteaArgs.owner, giteaArgs.repository, giteaArgs.personal), + bootstrap.WithBranch(bootstrapArgs.branch), + bootstrap.WithBootstrapTransportType("https"), + bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail), + bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix), + bootstrap.WithProviderTeamPermissions(mapTeamSlice(giteaArgs.teams, gtDefaultPermission)), + bootstrap.WithReadWriteKeyPermissions(giteaArgs.readWriteKey), + bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions), + bootstrap.WithLogger(logger), + bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), + } + if bootstrapArgs.sshHostname != "" { + bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) + } + if bootstrapArgs.tokenAuth { + bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https")) + } + if !giteaArgs.private { + bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public")) + } + if giteaArgs.reconcile { + bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile()) + } + + // Setup bootstrapper with constructed configs + b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...) + if err != nil { + return err + } + + // Run + return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout) +} diff --git a/cmd/flux/bootstrap_github.go b/cmd/flux/bootstrap_github.go index 8c7c214ee5..a82fb1ce4a 100644 --- a/cmd/flux/bootstrap_github.go +++ b/cmd/flux/bootstrap_github.go @@ -128,6 +128,13 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { return err } + if !bootstrapArgs.force { + err = confirmBootstrap(ctx, kubeClient) + if err != nil { + return err + } + } + // Manifest base if ver, err := getVersion(bootstrapArgs.version); err != nil { return err @@ -184,6 +191,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { Namespace: *kubeconfigArgs.Namespace, Components: bootstrapComponents(), Registry: bootstrapArgs.registry, + RegistryCredential: bootstrapArgs.registryCredential, ImagePullSecret: bootstrapArgs.imagePullSecret, WatchAllNamespaces: bootstrapArgs.watchAllNamespaces, NetworkPolicy: bootstrapArgs.networkPolicy, diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go index 6bb111c37f..15716623dc 100644 --- a/cmd/flux/bootstrap_gitlab.go +++ b/cmd/flux/bootstrap_gitlab.go @@ -64,7 +64,7 @@ the bootstrap command will perform an upgrade if needed.`, # Run bootstrap for a private repository hosted on a GitLab server flux bootstrap gitlab --owner= --repository= --hostname= --token-auth - # Run bootstrap for a an existing repository with a branch named main + # Run bootstrap for an existing repository with a branch named main flux bootstrap gitlab --owner= --repository= --branch=main --token-auth # Run bootstrap for a private repository using Deploy Token authentication @@ -145,6 +145,13 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { return err } + if !bootstrapArgs.force { + err = confirmBootstrap(ctx, kubeClient) + if err != nil { + return err + } + } + // Manifest base if ver, err := getVersion(bootstrapArgs.version); err != nil { return err @@ -209,6 +216,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { Namespace: *kubeconfigArgs.Namespace, Components: bootstrapComponents(), Registry: bootstrapArgs.registry, + RegistryCredential: bootstrapArgs.registryCredential, ImagePullSecret: bootstrapArgs.imagePullSecret, WatchAllNamespaces: bootstrapArgs.watchAllNamespaces, NetworkPolicy: bootstrapArgs.networkPolicy, diff --git a/cmd/flux/build_artifact.go b/cmd/flux/build_artifact.go index 4f26d40280..ce3caf0967 100644 --- a/cmd/flux/build_artifact.go +++ b/cmd/flux/build_artifact.go @@ -89,7 +89,7 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error { ociClient := oci.NewClient(oci.DefaultOptions()) if err := ociClient.Build(buildArtifactArgs.output, path, buildArtifactArgs.ignorePaths); err != nil { - return fmt.Errorf("bulding artifact failed, error: %w", err) + return fmt.Errorf("building artifact failed, error: %w", err) } logger.Successf("artifact created at %s", buildArtifactArgs.output) diff --git a/cmd/flux/build_kustomization.go b/cmd/flux/build_kustomization.go index ea0517c0dc..96fb5b9b93 100644 --- a/cmd/flux/build_kustomization.go +++ b/cmd/flux/build_kustomization.go @@ -21,10 +21,10 @@ import ( "os" "os/signal" - "github.com/fluxcd/pkg/ssa" "github.com/spf13/cobra" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + ssautil "github.com/fluxcd/pkg/ssa/utils" "github.com/fluxcd/flux2/v2/internal/build" ) @@ -63,6 +63,7 @@ type buildKsFlags struct { path string ignorePaths []string dryRun bool + strictSubst bool } var buildKsArgs buildKsFlags @@ -72,6 +73,8 @@ func init() { buildKsCmd.Flags().StringVar(&buildKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.") buildKsCmd.Flags().StringSliceVar(&buildKsArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in .gitignore format") buildKsCmd.Flags().BoolVar(&buildKsArgs.dryRun, "dry-run", false, "Dry run mode.") + buildKsCmd.Flags().BoolVar(&buildKsArgs.strictSubst, "strict-substitute", false, + "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.") buildCmd.AddCommand(buildKsCmd) } @@ -107,6 +110,7 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) { build.WithDryRun(buildKsArgs.dryRun), build.WithNamespace(*kubeconfigArgs.Namespace), build.WithIgnore(buildKsArgs.ignorePaths), + build.WithStrictSubstitute(buildKsArgs.strictSubst), ) } else { builder, err = build.NewBuilder(name, buildKsArgs.path, @@ -114,6 +118,7 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) { build.WithTimeout(rootArgs.timeout), build.WithKustomizationFile(buildKsArgs.kustomizationFile), build.WithIgnore(buildKsArgs.ignorePaths), + build.WithStrictSubstitute(buildKsArgs.strictSubst), ) } @@ -132,7 +137,7 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) { errChan <- err } - manifests, err := ssa.ObjectsToYAML(objects) + manifests, err := ssautil.ObjectsToYAML(objects) if err != nil { errChan <- err } diff --git a/cmd/flux/check.go b/cmd/flux/check.go index e6fa29a55e..b710894160 100644 --- a/cmd/flux/check.go +++ b/cmd/flux/check.go @@ -18,6 +18,7 @@ package main import ( "context" + "fmt" "os" "time" @@ -26,6 +27,7 @@ import ( v1 "k8s.io/api/apps/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/fluxcd/pkg/version" @@ -38,6 +40,7 @@ import ( var checkCmd = &cobra.Command{ Use: "check", + Args: cobra.NoArgs, Short: "Check requirements and installation", Long: withPreviewNote(`The check command will perform a series of checks to validate that the local environment is configured correctly and if the installed components are healthy.`), @@ -57,7 +60,7 @@ type checkFlags struct { } var kubernetesConstraints = []string{ - ">=1.24.0-0", + ">=1.28.0-0", } var checkArgs checkFlags @@ -80,7 +83,20 @@ func runCheckCmd(cmd *cobra.Command, args []string) error { fluxCheck() - if !kubernetesCheck(kubernetesConstraints) { + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions) + if err != nil { + return fmt.Errorf("Kubernetes client initialization failed: %s", err.Error()) + } + + kubeClient, err := client.New(cfg, client.Options{Scheme: utils.NewScheme()}) + if err != nil { + return err + } + + if !kubernetesCheck(cfg, kubernetesConstraints) { checkFailed = true } @@ -92,13 +108,18 @@ func runCheckCmd(cmd *cobra.Command, args []string) error { return nil } + logger.Actionf("checking version in cluster") + if !fluxClusterVersionCheck(ctx, kubeClient) { + checkFailed = true + } + logger.Actionf("checking controllers") - if !componentsCheck() { + if !componentsCheck(ctx, kubeClient) { checkFailed = true } logger.Actionf("checking crds") - if !crdsCheck() { + if !crdsCheck(ctx, kubeClient) { checkFailed = true } @@ -129,17 +150,11 @@ func fluxCheck() { return } if latestSv.GreaterThan(curSv) { - logger.Failuref("flux %s <%s (new version is available, please upgrade)", curSv, latestSv) + logger.Failuref("flux %s <%s (new CLI version is available, please upgrade)", curSv, latestSv) } } -func kubernetesCheck(constraints []string) bool { - cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions) - if err != nil { - logger.Failuref("Kubernetes client initialization failed: %s", err.Error()) - return false - } - +func kubernetesCheck(cfg *rest.Config, constraints []string) bool { clientSet, err := kubernetes.NewForConfig(cfg) if err != nil { logger.Failuref("Kubernetes client initialization failed: %s", err.Error()) @@ -178,21 +193,8 @@ func kubernetesCheck(constraints []string) bool { return true } -func componentsCheck() bool { - ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) - defer cancel() - - kubeConfig, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions) - if err != nil { - return false - } - - statusChecker, err := status.NewStatusChecker(kubeConfig, checkArgs.pollInterval, rootArgs.timeout, logger) - if err != nil { - return false - } - - kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) +func componentsCheck(ctx context.Context, kubeClient client.Client) bool { + statusChecker, err := status.NewStatusCheckerWithClient(kubeClient, checkArgs.pollInterval, rootArgs.timeout, logger) if err != nil { return false } @@ -222,15 +224,7 @@ func componentsCheck() bool { return ok } -func crdsCheck() bool { - ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) - defer cancel() - - kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) - if err != nil { - return false - } - +func crdsCheck(ctx context.Context, kubeClient client.Client) bool { ok := true selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue} var list apiextensionsv1.CustomResourceDefinitionList @@ -253,3 +247,17 @@ func crdsCheck() bool { } return ok } + +func fluxClusterVersionCheck(ctx context.Context, kubeClient client.Client) bool { + clusterInfo, err := getFluxClusterInfo(ctx, kubeClient) + if err != nil { + logger.Failuref("checking failed: %s", err.Error()) + return false + } + + if clusterInfo.distribution() != "" { + logger.Successf("distribution: %s", clusterInfo.distribution()) + } + logger.Successf("bootstrapped: %t", clusterInfo.bootstrapped) + return true +} diff --git a/cmd/flux/cluster_info.go b/cmd/flux/cluster_info.go new file mode 100644 index 0000000000..2f2f79a339 --- /dev/null +++ b/cmd/flux/cluster_info.go @@ -0,0 +1,126 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + + "github.com/manifoldco/promptui" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + + "github.com/fluxcd/flux2/v2/pkg/manifestgen" +) + +// bootstrapLabels are labels put on a resource by kustomize-controller. These labels on the CRD indicates +// that flux has been bootstrapped. +var bootstrapLabels = []string{ + fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group), + fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group), +} + +// fluxClusterInfo contains information about an existing flux installation on a cluster. +type fluxClusterInfo struct { + // bootstrapped indicates that Flux was installed using the `flux bootstrap` command. + bootstrapped bool + // managedBy is the name of the tool being used to manage the installation of Flux. + managedBy string + // partOf indicates which distribution the instance is a part of. + partOf string + // version is the Flux version number in semver format. + version string +} + +// getFluxClusterInfo returns information on the Flux installation running on the cluster. +// If an error occurred, the returned error will be non-nil. +// +// This function retrieves the GitRepository CRD from the cluster and checks it +// for a set of labels used to determine the Flux version and how Flux was installed. +// It returns the NotFound error from the underlying library if it was unable to find +// the GitRepository CRD and this can be used to check if Flux is installed. +func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, error) { + var info fluxClusterInfo + crdMetadata := &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: apiextensionsv1.SchemeGroupVersion.String(), + Kind: "CustomResourceDefinition", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("gitrepositories.%s", sourcev1.GroupVersion.Group), + }, + } + if err := c.Get(ctx, client.ObjectKeyFromObject(crdMetadata), crdMetadata); err != nil { + return info, err + } + + info.version = crdMetadata.Labels[manifestgen.VersionLabelKey] + + var present bool + for _, l := range bootstrapLabels { + _, present = crdMetadata.Labels[l] + } + if present { + info.bootstrapped = true + } + + // the `app.kubernetes.io/managed-by` label is not set by flux but might be set by other + // tools used to install Flux e.g Helm. + if manager, ok := crdMetadata.Labels["app.kubernetes.io/managed-by"]; ok { + info.managedBy = manager + } + + if partOf, ok := crdMetadata.Labels[manifestgen.PartOfLabelKey]; ok { + info.partOf = partOf + } + return info, nil +} + +// confirmFluxInstallOverride displays a prompt to the user so that they can confirm before overriding +// a Flux installation. It returns nil if the installation should continue, +// promptui.ErrAbort if the user doesn't confirm, or an error encountered. +func confirmFluxInstallOverride(info fluxClusterInfo) error { + // no need to display prompt if installation is managed by Flux + if installManagedByFlux(info.managedBy) { + return nil + } + + display := fmt.Sprintf("Flux %s has been installed on this cluster with %s!", info.version, info.managedBy) + fmt.Fprintln(rootCmd.ErrOrStderr(), display) + prompt := promptui.Prompt{ + Label: fmt.Sprintf("Are you sure you want to override the %s installation? Y/N", info.managedBy), + IsConfirm: true, + } + _, err := prompt.Run() + return err +} + +func (info fluxClusterInfo) distribution() string { + distribution := info.version + if info.partOf != "" { + distribution = fmt.Sprintf("%s-%s", info.partOf, info.version) + } + return distribution +} + +func installManagedByFlux(manager string) bool { + return manager == "" || manager == "flux" +} diff --git a/cmd/flux/cluster_info_test.go b/cmd/flux/cluster_info_test.go new file mode 100644 index 0000000000..86b94c9c6c --- /dev/null +++ b/cmd/flux/cluster_info_test.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "os" + "testing" + + . "github.com/onsi/gomega" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + ssautil "github.com/fluxcd/pkg/ssa/utils" +) + +func Test_getFluxClusterInfo(t *testing.T) { + g := NewWithT(t) + f, err := os.Open("./testdata/cluster_info/gitrepositories.yaml") + g.Expect(err).To(BeNil()) + + objs, err := ssautil.ReadObjects(f) + g.Expect(err).To(Not(HaveOccurred())) + gitrepo := objs[0] + + tests := []struct { + name string + labels map[string]string + wantErr bool + wantInfo fluxClusterInfo + }{ + { + name: "no git repository CRD present", + wantErr: true, + }, + { + name: "CRD with kustomize-controller labels", + labels: map[string]string{ + fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group): "flux-system", + fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group): "flux-system", + "app.kubernetes.io/version": "v2.1.0", + }, + wantInfo: fluxClusterInfo{ + version: "v2.1.0", + bootstrapped: true, + }, + }, + { + name: "CRD with kustomize-controller labels and managed-by label", + labels: map[string]string{ + fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group): "flux-system", + fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group): "flux-system", + "app.kubernetes.io/version": "v2.1.0", + "app.kubernetes.io/managed-by": "flux", + }, + wantInfo: fluxClusterInfo{ + version: "v2.1.0", + bootstrapped: true, + managedBy: "flux", + }, + }, + { + name: "CRD with only managed-by label", + labels: map[string]string{ + "app.kubernetes.io/version": "v2.1.0", + "app.kubernetes.io/managed-by": "helm", + }, + wantInfo: fluxClusterInfo{ + version: "v2.1.0", + managedBy: "helm", + }, + }, + { + name: "CRD with no labels", + labels: map[string]string{}, + wantInfo: fluxClusterInfo{}, + }, + { + name: "CRD with only version label", + labels: map[string]string{ + "app.kubernetes.io/version": "v2.1.0", + }, + wantInfo: fluxClusterInfo{ + version: "v2.1.0", + }, + }, + { + name: "CRD with version and part-of labels", + labels: map[string]string{ + "app.kubernetes.io/version": "v2.1.0", + "app.kubernetes.io/part-of": "flux", + }, + wantInfo: fluxClusterInfo{ + version: "v2.1.0", + partOf: "flux", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + newscheme := runtime.NewScheme() + apiextensionsv1.AddToScheme(newscheme) + builder := fake.NewClientBuilder().WithScheme(newscheme) + if tt.labels != nil { + gitrepo.SetLabels(tt.labels) + builder = builder.WithRuntimeObjects(gitrepo) + } + + client := builder.Build() + info, err := getFluxClusterInfo(context.Background(), client) + if tt.wantErr { + g.Expect(err).To(HaveOccurred()) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + } else { + g.Expect(err).To(Not(HaveOccurred())) + } + + g.Expect(info).To(BeEquivalentTo(tt.wantInfo)) + }) + } +} diff --git a/cmd/flux/create.go b/cmd/flux/create.go index d5e4fbcbcd..209c8f0a96 100644 --- a/cmd/flux/create.go +++ b/cmd/flux/create.go @@ -131,8 +131,8 @@ func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) e } logger.Waitingf("waiting for %s reconciliation", names.kind) - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isReady(ctx, kubeClient, namespacedName, object)); err != nil { + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, + isObjectReadyConditionFunc(kubeClient, namespacedName, object.asClientObject())); err != nil { return err } logger.Successf("%s reconciliation completed", names.kind) @@ -165,6 +165,6 @@ func parseLabels() (map[string]string, error) { } func validateObjectName(name string) bool { - r := regexp.MustCompile("^[a-z0-9]([a-z0-9\\-]){0,61}[a-z0-9]$") + r := regexp.MustCompile(`^[a-z0-9]([a-z0-9\-]){0,61}[a-z0-9]$`) return r.MatchString(name) } diff --git a/cmd/flux/create_alert.go b/cmd/flux/create_alert.go index a980c1fa48..80c8beee5b 100644 --- a/cmd/flux/create_alert.go +++ b/cmd/flux/create_alert.go @@ -22,14 +22,13 @@ import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/api/errors" - apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" notificationv1 "github.com/fluxcd/notification-controller/api/v1" - notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/flux2/v2/internal/utils" @@ -97,13 +96,13 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error { logger.Generatef("generating Alert") } - alert := notificationv1b2.Alert{ + alert := notificationv1b3.Alert{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: *kubeconfigArgs.Namespace, Labels: sourceLabels, }, - Spec: notificationv1b2.AlertSpec{ + Spec: notificationv1b3.AlertSpec{ ProviderRef: meta.LocalObjectReference{ Name: alertArgs.providerRef, }, @@ -132,8 +131,8 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error { } logger.Waitingf("waiting for Alert reconciliation") - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil { + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, + isStaticObjectReadyConditionFunc(kubeClient, namespacedName, &alert)); err != nil { return err } logger.Successf("Alert %s is ready", name) @@ -141,13 +140,13 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error { } func upsertAlert(ctx context.Context, kubeClient client.Client, - alert *notificationv1b2.Alert) (types.NamespacedName, error) { + alert *notificationv1b3.Alert) (types.NamespacedName, error) { namespacedName := types.NamespacedName{ Namespace: alert.GetNamespace(), Name: alert.GetName(), } - var existing notificationv1b2.Alert + var existing notificationv1b3.Alert err := kubeClient.Get(ctx, namespacedName, &existing) if err != nil { if errors.IsNotFound(err) { @@ -170,23 +169,3 @@ func upsertAlert(ctx context.Context, kubeClient client.Client, logger.Successf("Alert updated") return namespacedName, nil } - -func isAlertReady(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, alert *notificationv1b2.Alert) wait.ConditionFunc { - return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, alert) - if err != nil { - return false, err - } - - if c := apimeta.FindStatusCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil { - switch c.Status { - case metav1.ConditionTrue: - return true, nil - case metav1.ConditionFalse: - return false, fmt.Errorf(c.Message) - } - } - return false, nil - } -} diff --git a/cmd/flux/create_alertprovider.go b/cmd/flux/create_alertprovider.go index cef69baf89..89a8e28971 100644 --- a/cmd/flux/create_alertprovider.go +++ b/cmd/flux/create_alertprovider.go @@ -22,13 +22,12 @@ import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/api/errors" - apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/flux2/v2/internal/utils" @@ -127,8 +126,8 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error { } logger.Waitingf("waiting for Provider reconciliation") - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isAlertProviderReady(ctx, kubeClient, namespacedName, &provider)); err != nil { + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, + isStaticObjectReadyConditionFunc(kubeClient, namespacedName, &provider)); err != nil { return err } @@ -167,23 +166,3 @@ func upsertAlertProvider(ctx context.Context, kubeClient client.Client, logger.Successf("Provider updated") return namespacedName, nil } - -func isAlertProviderReady(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, provider *notificationv1.Provider) wait.ConditionFunc { - return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, provider) - if err != nil { - return false, err - } - - if c := apimeta.FindStatusCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil { - switch c.Status { - case metav1.ConditionTrue: - return true, nil - case metav1.ConditionFalse: - return false, fmt.Errorf(c.Message) - } - } - return false, nil - } -} diff --git a/cmd/flux/create_helmrelease.go b/cmd/flux/create_helmrelease.go index 3721c1e751..7e2bf3b32d 100644 --- a/cmd/flux/create_helmrelease.go +++ b/cmd/flux/create_helmrelease.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Flux authors +Copyright 2024 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,29 +24,30 @@ import ( "strings" "time" - "github.com/fluxcd/flux2/v2/internal/flags" - "github.com/fluxcd/flux2/v2/internal/utils" - "github.com/fluxcd/pkg/apis/meta" - "github.com/fluxcd/pkg/runtime/transform" - "github.com/spf13/cobra" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/errors" - apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/transform" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" + + "github.com/fluxcd/flux2/v2/internal/flags" + "github.com/fluxcd/flux2/v2/internal/utils" ) var createHelmReleaseCmd = &cobra.Command{ Use: "helmrelease [name]", Aliases: []string{"hr"}, Short: "Create or update a HelmRelease resource", - Long: withPreviewNote(`The helmrelease create command generates a HelmRelease resource for a given HelmRepository source.`), + Long: `The helmrelease create command generates a HelmRelease resource for a given HelmRepository source.`, Example: ` # Create a HelmRelease with a chart from a HelmRepository source flux create hr podinfo \ --interval=10m \ @@ -105,7 +106,17 @@ var createHelmReleaseCmd = &cobra.Command{ --source=HelmRepository/podinfo \ --chart=podinfo \ --values=./values.yaml \ - --export > podinfo-release.yaml`, + --export > podinfo-release.yaml + + # Create a HelmRelease using a chart from a HelmChart resource + flux create hr podinfo \ + --namespace=default \ + --chart-ref=HelmChart/podinfo.flux-system \ + + # Create a HelmRelease using a chart from an OCIRepository resource + flux create hr podinfo \ + --namespace=default \ + --chart-ref=OCIRepository/podinfo.flux-system`, RunE: createHelmReleaseCmdRun, } @@ -115,6 +126,7 @@ type helmReleaseFlags struct { dependsOn []string chart string chartVersion string + chartRef string targetNamespace string createNamespace bool valuesFiles []string @@ -130,6 +142,8 @@ var helmReleaseArgs helmReleaseFlags var supportedHelmReleaseValuesFromKinds = []string{"Secret", "ConfigMap"} +var supportedHelmReleaseReferenceKinds = []string{sourcev1b2.OCIRepositoryKind, sourcev1.HelmChartKind} + func init() { createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.name, "release-name", "", "name used for the Helm release, defaults to a composition of '[-]'") createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.source, "source", helmReleaseArgs.source.Description()) @@ -145,14 +159,15 @@ func init() { createHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.valuesFrom, "values-from", nil, "a Kubernetes object reference that contains the values.yaml data key in the format '/', where kind must be one of: (Secret,ConfigMap)") createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.crds, "crds", helmReleaseArgs.crds.Description()) createHelmReleaseCmd.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") + createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.chartRef, "chart-ref", "", "the name of the HelmChart resource to use as source for the HelmRelease, in the format '/.', where kind must be one of: (OCIRepository,HelmChart)") createCmd.AddCommand(createHelmReleaseCmd) } func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error { name := args[0] - if helmReleaseArgs.chart == "" { - return fmt.Errorf("chart name or path is required") + if helmReleaseArgs.chart == "" && helmReleaseArgs.chartRef == "" { + return fmt.Errorf("chart or chart-ref is required") } sourceLabels, err := parseLabels() @@ -182,21 +197,40 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error { Duration: createArgs.interval, }, TargetNamespace: helmReleaseArgs.targetNamespace, + Suspend: false, + }, + } - Chart: helmv2.HelmChartTemplate{ - Spec: helmv2.HelmChartTemplateSpec{ - Chart: helmReleaseArgs.chart, - Version: helmReleaseArgs.chartVersion, - SourceRef: helmv2.CrossNamespaceObjectReference{ - Kind: helmReleaseArgs.source.Kind, - Name: helmReleaseArgs.source.Name, - Namespace: helmReleaseArgs.source.Namespace, - }, - ReconcileStrategy: helmReleaseArgs.reconcileStrategy, + switch { + case helmReleaseArgs.chart != "": + helmRelease.Spec.Chart = &helmv2.HelmChartTemplate{ + Spec: helmv2.HelmChartTemplateSpec{ + Chart: helmReleaseArgs.chart, + Version: helmReleaseArgs.chartVersion, + SourceRef: helmv2.CrossNamespaceObjectReference{ + Kind: helmReleaseArgs.source.Kind, + Name: helmReleaseArgs.source.Name, + Namespace: helmReleaseArgs.source.Namespace, }, + ReconcileStrategy: helmReleaseArgs.reconcileStrategy, }, - Suspend: false, - }, + } + if helmReleaseArgs.chartInterval != 0 { + helmRelease.Spec.Chart.Spec.Interval = &metav1.Duration{ + Duration: helmReleaseArgs.chartInterval, + } + } + case helmReleaseArgs.chartRef != "": + kind, name, ns := utils.ParseObjectKindNameNamespace(helmReleaseArgs.chartRef) + if kind != sourcev1.HelmChartKind && kind != sourcev1b2.OCIRepositoryKind { + return fmt.Errorf("chart reference kind '%s' is not supported, must be one of: %s", + kind, strings.Join(supportedHelmReleaseReferenceKinds, ", ")) + } + helmRelease.Spec.ChartRef = &helmv2.CrossNamespaceSourceReference{ + Kind: kind, + Name: name, + Namespace: ns, + } } if helmReleaseArgs.kubeConfigSecretRef != "" { @@ -207,12 +241,6 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error { } } - if helmReleaseArgs.chartInterval != 0 { - helmRelease.Spec.Chart.Spec.Interval = &metav1.Duration{ - Duration: helmReleaseArgs.chartInterval, - } - } - if helmReleaseArgs.createNamespace { if helmRelease.Spec.Install == nil { helmRelease.Spec.Install = &helmv2.Install{} @@ -303,13 +331,13 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error { } logger.Waitingf("waiting for HelmRelease reconciliation") - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isHelmReleaseReady(ctx, kubeClient, namespacedName, &helmRelease)); err != nil { + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, + isObjectReadyConditionFunc(kubeClient, namespacedName, &helmRelease)); err != nil { return err } logger.Successf("HelmRelease %s is ready", name) - logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision) + logger.Successf("applied revision %s", getHelmReleaseRevision(helmRelease)) return nil } @@ -344,23 +372,6 @@ func upsertHelmRelease(ctx context.Context, kubeClient client.Client, return namespacedName, nil } -func isHelmReleaseReady(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) wait.ConditionFunc { - return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, helmRelease) - if err != nil { - return false, err - } - - // Confirm the state we are observing is for the current generation - if helmRelease.Generation != helmRelease.Status.ObservedGeneration { - return false, nil - } - - return apimeta.IsStatusConditionTrue(helmRelease.Status.Conditions, meta.ReadyCondition), nil - } -} - func validateStrategy(input string) bool { allowedStrategy := []string{"Revision", "ChartVersion"} diff --git a/cmd/flux/create_helmrelease_test.go b/cmd/flux/create_helmrelease_test.go new file mode 100644 index 0000000000..ffdef081d0 --- /dev/null +++ b/cmd/flux/create_helmrelease_test.go @@ -0,0 +1,86 @@ +//go:build unit +// +build unit + +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import "testing" + +func TestCreateHelmRelease(t *testing.T) { + tmpl := map[string]string{ + "fluxns": allocateNamespace("flux-system"), + } + setupHRSource(t, tmpl) + + tests := []struct { + name string + args string + assert assertFunc + }{ + { + name: "missing name", + args: "create helmrelease --export", + assert: assertError("name is required"), + }, + { + name: "missing chart template and chartRef", + args: "create helmrelease podinfo --export", + assert: assertError("chart or chart-ref is required"), + }, + { + name: "unknown source kind", + args: "create helmrelease podinfo --source foobar/podinfo --chart podinfo --export", + assert: assertError(`invalid argument "foobar/podinfo" for "--source" flag: source kind 'foobar' is not supported, must be one of: HelmRepository, GitRepository, Bucket`), + }, + { + name: "unknown chart reference kind", + args: "create helmrelease podinfo --chart-ref foobar/podinfo --export", + assert: assertError(`chart reference kind 'foobar' is not supported, must be one of: OCIRepository, HelmChart`), + }, + { + name: "basic helmrelease", + args: "create helmrelease podinfo --source Helmrepository/podinfo --chart podinfo --interval=1m0s --export", + assert: assertGoldenTemplateFile("testdata/create_hr/basic.yaml", tmpl), + }, + { + name: "chart with OCIRepository source", + args: "create helmrelease podinfo --chart-ref OCIRepository/podinfo --interval=1m0s --export", + assert: assertGoldenTemplateFile("testdata/create_hr/or_basic.yaml", tmpl), + }, + { + name: "chart with HelmChart source", + args: "create helmrelease podinfo --chart-ref HelmChart/podinfo --interval=1m0s --export", + assert: assertGoldenTemplateFile("testdata/create_hr/hc_basic.yaml", tmpl), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := cmdTestCase{ + args: tt.args + " -n " + tmpl["fluxns"], + assert: tt.assert, + } + cmd.runTestCmd(t) + }) + } +} + +func setupHRSource(t *testing.T, tmpl map[string]string) { + t.Helper() + testEnv.CreateObjectFile("./testdata/create_hr/setup-source.yaml", tmpl, t) +} diff --git a/cmd/flux/create_image_policy.go b/cmd/flux/create_image_policy.go index 116a21521d..db287ed2db 100644 --- a/cmd/flux/create_image_policy.go +++ b/cmd/flux/create_image_policy.go @@ -54,13 +54,12 @@ the status of the object.`), RunE: createImagePolicyRun} type imagePolicyFlags struct { - imageRef string - semver string - alpha string - numeric string - filterRegex string - filterExtract string - filterNumerical string + imageRef string + semver string + alpha string + numeric string + filterRegex string + filterExtract string } var imagePolicyArgs = imagePolicyFlags{} @@ -183,7 +182,6 @@ func validateExtractStr(template string, capNames []string) error { name, num, rest, ok := extract(template) if !ok { // Malformed extract string, assume user didn't want this - template = template[1:] return fmt.Errorf("--filter-extract is malformed") } template = rest diff --git a/cmd/flux/create_image_update.go b/cmd/flux/create_image_update.go index c1036dfa38..8e4e7366be 100644 --- a/cmd/flux/create_image_update.go +++ b/cmd/flux/create_image_update.go @@ -22,7 +22,7 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" sourcev1 "github.com/fluxcd/source-controller/api/v1" ) diff --git a/cmd/flux/create_kustomization.go b/cmd/flux/create_kustomization.go index fea9d83660..6c662928cc 100644 --- a/cmd/flux/create_kustomization.go +++ b/cmd/flux/create_kustomization.go @@ -24,13 +24,12 @@ import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/api/errors" - apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" "github.com/fluxcd/pkg/apis/meta" @@ -263,8 +262,8 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error { } logger.Waitingf("waiting for Kustomization reconciliation") - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isKustomizationReady(ctx, kubeClient, namespacedName, &kustomization)); err != nil { + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, + isObjectReadyConditionFunc(kubeClient, namespacedName, &kustomization)); err != nil { return err } logger.Successf("Kustomization %s is ready", name) @@ -303,28 +302,3 @@ func upsertKustomization(ctx context.Context, kubeClient client.Client, logger.Successf("Kustomization updated") return namespacedName, nil } - -func isKustomizationReady(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) wait.ConditionFunc { - return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, kustomization) - if err != nil { - return false, err - } - - // Confirm the state we are observing is for the current generation - if kustomization.Generation != kustomization.Status.ObservedGeneration { - return false, nil - } - - if c := apimeta.FindStatusCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil { - switch c.Status { - case metav1.ConditionTrue: - return true, nil - case metav1.ConditionFalse: - return false, fmt.Errorf(c.Message) - } - } - return false, nil - } -} diff --git a/cmd/flux/create_receiver.go b/cmd/flux/create_receiver.go index 5597dae255..ad6436cfcd 100644 --- a/cmd/flux/create_receiver.go +++ b/cmd/flux/create_receiver.go @@ -22,7 +22,6 @@ import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/api/errors" - apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" @@ -139,8 +138,8 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error { } logger.Waitingf("waiting for Receiver reconciliation") - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil { + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, + isObjectReadyConditionFunc(kubeClient, namespacedName, &receiver)); err != nil { return err } logger.Successf("Receiver %s is ready", name) @@ -179,23 +178,3 @@ func upsertReceiver(ctx context.Context, kubeClient client.Client, logger.Successf("Receiver updated") return namespacedName, nil } - -func isReceiverReady(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, receiver *notificationv1.Receiver) wait.ConditionFunc { - return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, receiver) - if err != nil { - return false, err - } - - if c := apimeta.FindStatusCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil { - switch c.Status { - case metav1.ConditionTrue: - return true, nil - case metav1.ConditionFalse: - return false, fmt.Errorf(c.Message) - } - } - return false, nil - } -} diff --git a/cmd/flux/create_secret_git.go b/cmd/flux/create_secret_git.go index a9330cffbd..9865cbc988 100644 --- a/cmd/flux/create_secret_git.go +++ b/cmd/flux/create_secret_git.go @@ -88,6 +88,7 @@ type secretGitFlags struct { rsaBits flags.RSAKeyBits ecdsaCurve flags.ECDSACurve caFile string + caCrtFile string privateKeyFile string bearerToken string } @@ -102,6 +103,7 @@ func init() { createSecretGitCmd.Flags().Var(&secretGitArgs.rsaBits, "ssh-rsa-bits", secretGitArgs.rsaBits.Description()) createSecretGitCmd.Flags().Var(&secretGitArgs.ecdsaCurve, "ssh-ecdsa-curve", secretGitArgs.ecdsaCurve.Description()) createSecretGitCmd.Flags().StringVar(&secretGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates") + createSecretGitCmd.Flags().StringVar(&secretGitArgs.caCrtFile, "ca-crt-file", "", "path to TLS CA certificate file used for validating self-signed certificates; takes precedence over --ca-file") createSecretGitCmd.Flags().StringVar(&secretGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server") createSecretGitCmd.Flags().StringVar(&secretGitArgs.bearerToken, "bearer-token", "", "bearer authentication token") @@ -160,12 +162,18 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error { if secretGitArgs.username != "" && secretGitArgs.password != "" && secretGitArgs.bearerToken != "" { return fmt.Errorf("user credentials and bearer token cannot be used together") } - if secretGitArgs.caFile != "" { - caBundle, err := os.ReadFile(secretGitArgs.caFile) + + // --ca-crt-file takes precedence over --ca-file. + if secretGitArgs.caCrtFile != "" { + opts.CACrt, err = os.ReadFile(secretGitArgs.caCrtFile) + if err != nil { + return fmt.Errorf("unable to read TLS CA file: %w", err) + } + } else if secretGitArgs.caFile != "" { + opts.CAFile, err = os.ReadFile(secretGitArgs.caFile) if err != nil { return fmt.Errorf("unable to read TLS CA file: %w", err) } - opts.CAFile = caBundle } default: return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme) diff --git a/cmd/flux/create_secret_git_test.go b/cmd/flux/create_secret_git_test.go index bdf2431d41..d4c84d0db5 100644 --- a/cmd/flux/create_secret_git_test.go +++ b/cmd/flux/create_secret_git_test.go @@ -1,10 +1,21 @@ package main import ( + "fmt" + "os" "testing" ) func TestCreateGitSecret(t *testing.T) { + file, err := os.CreateTemp(t.TempDir(), "ca-crt") + if err != nil { + t.Fatal("could not create CA certificate file") + } + _, err = file.Write([]byte("ca-data")) + if err != nil { + t.Fatal("could not write to CA certificate file") + } + tests := []struct { name string args string @@ -35,6 +46,11 @@ func TestCreateGitSecret(t *testing.T) { args: "create secret git bearer-token-auth --url=https://github.com/stefanprodan/podinfo --bearer-token=ghp_baR2qnFF0O41WlucePL3udt2N9vVZS4R0hAS --namespace=my-namespace --export", assert: assertGoldenFile("testdata/create_secret/git/git-bearer-token.yaml"), }, + { + name: "git authentication with CA certificate", + args: 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()), + assert: assertGoldenFile("testdata/create_secret/git/secret-ca-crt.yaml"), + }, { name: "git authentication with basic auth and bearer token", args: "create secret git podinfo-auth --url=https://github.com/stefanprodan/podinfo --username=aaa --password=zzzz --bearer-token=aaaa --namespace=my-namespace --export", diff --git a/cmd/flux/create_secret_helm.go b/cmd/flux/create_secret_helm.go index bf733adbc9..2a809366d1 100644 --- a/cmd/flux/create_secret_helm.go +++ b/cmd/flux/create_secret_helm.go @@ -32,7 +32,7 @@ import ( var createSecretHelmCmd = &cobra.Command{ Use: "helm [name]", Short: "Create or update a Kubernetes secret for Helm repository authentication", - Long: withPreviewNote(`The create secret helm command generates a Kubernetes secret with basic authentication credentials.`), + Long: `The create secret helm command generates a Kubernetes secret with basic authentication credentials.`, Example: ` # Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS flux create secret helm repo-auth \ --namespace=my-namespace \ @@ -41,15 +41,8 @@ var createSecretHelmCmd = &cobra.Command{ --export > repo-auth.yaml sops --encrypt --encrypted-regex '^(data|stringData)$' \ - --in-place repo-auth.yaml + --in-place repo-auth.yaml`, - # Create a Helm authentication secret using a custom TLS cert - flux create secret helm repo-auth \ - --username=username \ - --password=password \ - --cert-file=./cert.crt \ - --key-file=./key.crt \ - --ca-file=./ca.crt`, RunE: createSecretHelmCmdRun, } @@ -62,9 +55,16 @@ type secretHelmFlags struct { var secretHelmArgs secretHelmFlags func init() { - createSecretHelmCmd.Flags().StringVarP(&secretHelmArgs.username, "username", "u", "", "basic authentication username") - createSecretHelmCmd.Flags().StringVarP(&secretHelmArgs.password, "password", "p", "", "basic authentication password") - initSecretTLSFlags(createSecretHelmCmd.Flags(), &secretHelmArgs.secretTLSFlags) + flags := createSecretHelmCmd.Flags() + flags.StringVarP(&secretHelmArgs.username, "username", "u", "", "basic authentication username") + flags.StringVarP(&secretHelmArgs.password, "password", "p", "", "basic authentication password") + + initSecretDeprecatedTLSFlags(flags, &secretHelmArgs.secretTLSFlags) + deprecationMsg := "please use the command `flux create secret tls` to generate TLS secrets" + flags.MarkDeprecated("cert-file", deprecationMsg) + flags.MarkDeprecated("key-file", deprecationMsg) + flags.MarkDeprecated("ca-file", deprecationMsg) + createSecretCmd.AddCommand(createSecretHelmCmd) } diff --git a/cmd/flux/create_secret_notation.go b/cmd/flux/create_secret_notation.go new file mode 100644 index 0000000000..dae49a4c41 --- /dev/null +++ b/cmd/flux/create_secret_notation.go @@ -0,0 +1,161 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/fluxcd/flux2/v2/internal/utils" + "github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" + "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" +) + +var createSecretNotationCmd = &cobra.Command{ + Use: "notation [name]", + Short: "Create or update a Kubernetes secret for verifications of artifacts signed by Notation", + Long: withPreviewNote(`The create secret notation command generates a Kubernetes secret with root ca certificates and trust policy.`), + Example: ` # Create a Notation configuration secret on disk and encrypt it with Mozilla SOPS + flux create secret notation my-notation-cert \ + --namespace=my-namespace \ + --trust-policy-file=./my-trust-policy.json \ + --ca-cert-file=./my-cert.crt \ + --export > my-notation-cert.yaml + + sops --encrypt --encrypted-regex '^(data|stringData)$' \ + --in-place my-notation-cert.yaml`, + + RunE: createSecretNotationCmdRun, +} + +type secretNotationFlags struct { + trustPolicyFile string + caCrtFile []string +} + +var secretNotationArgs secretNotationFlags + +func init() { + createSecretNotationCmd.Flags().StringVar(&secretNotationArgs.trustPolicyFile, "trust-policy-file", "", "notation trust policy file path") + createSecretNotationCmd.Flags().StringSliceVar(&secretNotationArgs.caCrtFile, "ca-cert-file", []string{}, "root ca cert file path") + + createSecretCmd.AddCommand(createSecretNotationCmd) +} + +func createSecretNotationCmdRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("name is required") + } + + if secretNotationArgs.caCrtFile == nil || len(secretNotationArgs.caCrtFile) == 0 { + return fmt.Errorf("--ca-cert-file is required") + } + + if secretNotationArgs.trustPolicyFile == "" { + return fmt.Errorf("--trust-policy-file is required") + } + + name := args[0] + + labels, err := parseLabels() + if err != nil { + return err + } + + policy, err := os.ReadFile(secretNotationArgs.trustPolicyFile) + if err != nil { + return fmt.Errorf("unable to read trust policy file: %w", err) + } + + var doc trustpolicy.Document + + if err := json.Unmarshal(policy, &doc); err != nil { + return fmt.Errorf("failed to unmarshal trust policy %s: %w", secretNotationArgs.trustPolicyFile, err) + } + + if err := doc.Validate(); err != nil { + return fmt.Errorf("invalid trust policy: %w", err) + } + + var ( + caCerts []sourcesecret.VerificationCrt + fileErr error + ) + for _, caCrtFile := range secretNotationArgs.caCrtFile { + fileName := filepath.Base(caCrtFile) + if !strings.HasSuffix(fileName, ".crt") && !strings.HasSuffix(fileName, ".pem") { + fileErr = errors.Join(fileErr, fmt.Errorf("%s must end with either .crt or .pem", fileName)) + continue + } + caBundle, err := os.ReadFile(caCrtFile) + if err != nil { + fileErr = errors.Join(fileErr, fmt.Errorf("unable to read TLS CA file: %w", err)) + continue + } + caCerts = append(caCerts, sourcesecret.VerificationCrt{Name: fileName, CACrt: caBundle}) + } + + if fileErr != nil { + return fileErr + } + + if len(caCerts) == 0 { + return fmt.Errorf("no CA certs found") + } + + opts := sourcesecret.Options{ + Name: name, + Namespace: *kubeconfigArgs.Namespace, + Labels: labels, + VerificationCrts: caCerts, + TrustPolicy: policy, + } + secret, err := sourcesecret.Generate(opts) + if err != nil { + return err + } + + if createArgs.export { + rootCmd.Println(secret.Content) + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) + if err != nil { + return err + } + var s corev1.Secret + if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil { + return err + } + if err := upsertSecret(ctx, kubeClient, s); err != nil { + return err + } + + logger.Actionf("notation configuration secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace) + return nil +} diff --git a/cmd/flux/create_secret_notation_test.go b/cmd/flux/create_secret_notation_test.go new file mode 100644 index 0000000000..c5944c38d0 --- /dev/null +++ b/cmd/flux/create_secret_notation_test.go @@ -0,0 +1,124 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + "path/filepath" + "testing" +) + +const ( + trustPolicy = "./testdata/create_secret/notation/test-trust-policy.json" + invalidTrustPolicy = "./testdata/create_secret/notation/invalid-trust-policy.json" + invalidJson = "./testdata/create_secret/notation/invalid.json" + testCertFolder = "./testdata/create_secret/notation" +) + +func TestCreateNotationSecret(t *testing.T) { + crt, err := os.Create(filepath.Join(t.TempDir(), "ca.crt")) + if err != nil { + t.Fatal("could not create ca.crt file") + } + + pem, err := os.Create(filepath.Join(t.TempDir(), "ca.pem")) + if err != nil { + t.Fatal("could not create ca.pem file") + } + + invalidCert, err := os.Create(filepath.Join(t.TempDir(), "ca.p12")) + if err != nil { + t.Fatal("could not create ca.p12 file") + } + + _, err = crt.Write([]byte("ca-data-crt")) + if err != nil { + t.Fatal("could not write to crt certificate file") + } + + _, err = pem.Write([]byte("ca-data-pem")) + if err != nil { + t.Fatal("could not write to pem certificate file") + } + + tests := []struct { + name string + args string + assert assertFunc + }{ + { + name: "no args", + args: "create secret notation", + assert: assertError("name is required"), + }, + { + name: "no trust policy", + args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s", testCertFolder), + assert: assertError("--trust-policy-file is required"), + }, + { + name: "no cert", + args: fmt.Sprintf("create secret notation notation-config --trust-policy-file=%s", trustPolicy), + assert: assertError("--ca-cert-file is required"), + }, + { + name: "non pem and crt cert", + args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s", invalidCert.Name(), trustPolicy), + assert: assertError("ca.p12 must end with either .crt or .pem"), + }, + { + name: "invalid trust policy", + args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s", t.TempDir(), invalidTrustPolicy), + assert: assertError("invalid trust policy: a trust policy statement is missing a name, every statement requires a name"), + }, + { + name: "invalid trust policy json", + args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s", t.TempDir(), invalidJson), + assert: assertError(fmt.Sprintf("failed to unmarshal trust policy %s: json: cannot unmarshal string into Go value of type trustpolicy.Document", invalidJson)), + }, + { + name: "crt secret", + args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s --namespace=my-namespace --export", crt.Name(), trustPolicy), + assert: assertGoldenFile("./testdata/create_secret/notation/secret-ca-crt.yaml"), + }, + { + name: "pem secret", + args: fmt.Sprintf("create secret notation notation-config --ca-cert-file=%s --trust-policy-file=%s --namespace=my-namespace --export", pem.Name(), trustPolicy), + assert: assertGoldenFile("./testdata/create_secret/notation/secret-ca-pem.yaml"), + }, + { + name: "multi secret", + args: 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), + assert: assertGoldenFile("./testdata/create_secret/notation/secret-ca-multi.yaml"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + secretNotationArgs = secretNotationFlags{} + }() + + cmd := cmdTestCase{ + args: tt.args, + assert: tt.assert, + } + cmd.runTestCmd(t) + }) + } +} diff --git a/cmd/flux/create_secret_tls.go b/cmd/flux/create_secret_tls.go index ffe756512b..82f36743dd 100644 --- a/cmd/flux/create_secret_tls.go +++ b/cmd/flux/create_secret_tls.go @@ -38,8 +38,9 @@ var createSecretTLSCmd = &cobra.Command{ # Files are expected to be PEM-encoded. flux create secret tls certs \ --namespace=my-namespace \ - --cert-file=./client.crt \ - --key-file=./client.key \ + --tls-crt-file=./client.crt \ + --tls-key-file=./client.key \ + --ca-crt-file=./ca.crt \ --export > certs.yaml sops --encrypt --encrypted-regex '^(data|stringData)$' \ @@ -48,22 +49,37 @@ var createSecretTLSCmd = &cobra.Command{ } type secretTLSFlags struct { - certFile string - keyFile string - caFile string + certFile string + keyFile string + caFile string + caCrtFile string + tlsKeyFile string + tlsCrtFile string } var secretTLSArgs secretTLSFlags -func initSecretTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) { +func initSecretDeprecatedTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) { flags.StringVar(&args.certFile, "cert-file", "", "TLS authentication cert file path") flags.StringVar(&args.keyFile, "key-file", "", "TLS authentication key file path") flags.StringVar(&args.caFile, "ca-file", "", "TLS authentication CA file path") } +func initSecretTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) { + flags.StringVar(&args.tlsCrtFile, "tls-crt-file", "", "TLS authentication cert file path") + flags.StringVar(&args.tlsKeyFile, "tls-key-file", "", "TLS authentication key file path") + flags.StringVar(&args.caCrtFile, "ca-crt-file", "", "TLS authentication CA file path") +} + func init() { flags := createSecretTLSCmd.Flags() + initSecretDeprecatedTLSFlags(flags, &secretTLSArgs) initSecretTLSFlags(flags, &secretTLSArgs) + + flags.MarkDeprecated("cert-file", "please use --tls-crt-file instead") + flags.MarkDeprecated("key-file", "please use --tls-key-file instead") + flags.MarkDeprecated("ca-file", "please use --ca-crt-file instead") + createSecretCmd.AddCommand(createSecretTLSCmd) } @@ -75,33 +91,40 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error { return err } - caBundle := []byte{} - if secretTLSArgs.caFile != "" { - var err error - caBundle, err = os.ReadFile(secretTLSArgs.caFile) + opts := sourcesecret.Options{ + Name: name, + Namespace: *kubeconfigArgs.Namespace, + Labels: labels, + } + + if secretTLSArgs.caCrtFile != "" { + opts.CACrt, err = os.ReadFile(secretTLSArgs.caCrtFile) + if err != nil { + return fmt.Errorf("unable to read TLS CA file: %w", err) + } + } else if secretTLSArgs.caFile != "" { + opts.CAFile, err = os.ReadFile(secretTLSArgs.caFile) if err != nil { return fmt.Errorf("unable to read TLS CA file: %w", err) } } - var certFile, keyFile []byte - if secretTLSArgs.certFile != "" && secretTLSArgs.keyFile != "" { - if certFile, err = os.ReadFile(secretTLSArgs.certFile); err != nil { + if secretTLSArgs.tlsCrtFile != "" && secretTLSArgs.tlsKeyFile != "" { + if opts.TLSCrt, err = os.ReadFile(secretTLSArgs.tlsCrtFile); err != nil { + return fmt.Errorf("failed to read cert file: %w", err) + } + if opts.TLSKey, err = os.ReadFile(secretTLSArgs.tlsKeyFile); err != nil { + return fmt.Errorf("failed to read key file: %w", err) + } + } else if secretTLSArgs.certFile != "" && secretTLSArgs.keyFile != "" { + if opts.CertFile, err = os.ReadFile(secretTLSArgs.certFile); err != nil { return fmt.Errorf("failed to read cert file: %w", err) } - if keyFile, err = os.ReadFile(secretTLSArgs.keyFile); err != nil { + if opts.KeyFile, err = os.ReadFile(secretTLSArgs.keyFile); err != nil { return fmt.Errorf("failed to read key file: %w", err) } } - opts := sourcesecret.Options{ - Name: name, - Namespace: *kubeconfigArgs.Namespace, - Labels: labels, - CAFile: caBundle, - CertFile: certFile, - KeyFile: keyFile, - } secret, err := sourcesecret.Generate(opts) if err != nil { return err diff --git a/cmd/flux/create_secret_tls_test.go b/cmd/flux/create_secret_tls_test.go index 31d4920046..226ed8e3c3 100644 --- a/cmd/flux/create_secret_tls_test.go +++ b/cmd/flux/create_secret_tls_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -func TestCreateTlsSecretNoArgs(t *testing.T) { +func TestCreateTlsSecret(t *testing.T) { tests := []struct { name string args string @@ -15,9 +15,13 @@ func TestCreateTlsSecretNoArgs(t *testing.T) { assert: assertError("name is required"), }, { - args: "create secret tls certs --namespace=my-namespace --cert-file=./testdata/create_secret/tls/test-cert.pem --key-file=./testdata/create_secret/tls/test-key.pem --export", + args: "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", assert: assertGoldenFile("testdata/create_secret/tls/secret-tls.yaml"), }, + { + args: "create secret tls certs --namespace=my-namespace --cert-file=./testdata/create_secret/tls/test-cert.pem --key-file=./testdata/create_secret/tls/test-key.pem --ca-file=./testdata/create_secret/tls/test-ca.pem --export", + assert: assertGoldenFile("testdata/create_secret/tls/deprecated-secret-tls.yaml"), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/flux/create_source_bucket.go b/cmd/flux/create_source_bucket.go index 68ef8f3e13..32c88730f4 100644 --- a/cmd/flux/create_source_bucket.go +++ b/cmd/flux/create_source_bucket.go @@ -31,7 +31,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/fluxcd/pkg/apis/meta" - "github.com/fluxcd/pkg/runtime/conditions" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" @@ -204,8 +203,8 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error { } logger.Waitingf("waiting for Bucket source reconciliation") - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isBucketReady(ctx, kubeClient, namespacedName, bucket)); err != nil { + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, + isObjectReadyConditionFunc(kubeClient, namespacedName, bucket)); err != nil { return err } logger.Successf("Bucket source reconciliation completed") @@ -247,30 +246,3 @@ func upsertBucket(ctx context.Context, kubeClient client.Client, logger.Successf("Bucket source updated") return namespacedName, nil } - -func isBucketReady(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionFunc { - return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, bucket) - if err != nil { - return false, err - } - - if c := conditions.Get(bucket, meta.ReadyCondition); c != nil { - // Confirm the Ready condition we are observing is for the - // current generation - if c.ObservedGeneration != bucket.GetGeneration() { - return false, nil - } - - // Further check the Status - switch c.Status { - case metav1.ConditionTrue: - return true, nil - case metav1.ConditionFalse: - return false, fmt.Errorf(c.Message) - } - } - return false, nil - } -} diff --git a/cmd/flux/create_source_chart.go b/cmd/flux/create_source_chart.go new file mode 100644 index 0000000000..aab0219687 --- /dev/null +++ b/cmd/flux/create_source_chart.go @@ -0,0 +1,217 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + + "github.com/fluxcd/flux2/v2/internal/flags" + "github.com/fluxcd/flux2/v2/internal/utils" +) + +var createSourceChartCmd = &cobra.Command{ + Use: "chart [name]", + Short: "Create or update a HelmChart source", + Long: `The create source chart command generates a HelmChart resource and waits for the chart to be available.`, + Example: ` # Create a source for a chart residing in a HelmRepository + flux create source chart podinfo \ + --source=HelmRepository/podinfo \ + --chart=podinfo \ + --chart-version=6.x + + # Create a source for a chart residing in a Git repository + flux create source chart podinfo \ + --source=GitRepository/podinfo \ + --chart=./charts/podinfo + + # Create a source for a chart residing in a S3 Bucket + flux create source chart podinfo \ + --source=Bucket/podinfo \ + --chart=./charts/podinfo + + # Create a source for a chart from OCI and verify its signature + flux create source chart podinfo \ + --source HelmRepository/podinfo \ + --chart podinfo \ + --chart-version=6.6.2 \ + --verify-provider=cosign \ + --verify-issuer=https://token.actions.githubusercontent.com \ + --verify-subject=https://github.com/stefanprodan/podinfo/.github/workflows/release.yml@refs/tags/6.6.2`, + RunE: createSourceChartCmdRun, +} + +type sourceChartFlags struct { + chart string + chartVersion string + source flags.LocalHelmChartSource + reconcileStrategy string + verifyProvider flags.SourceOCIVerifyProvider + verifySecretRef string + verifyOIDCIssuer string + verifySubject string +} + +var sourceChartArgs sourceChartFlags + +func init() { + createSourceChartCmd.Flags().StringVar(&sourceChartArgs.chart, "chart", "", "Helm chart name or path") + createSourceChartCmd.Flags().StringVar(&sourceChartArgs.chartVersion, "chart-version", "", "Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)") + createSourceChartCmd.Flags().Var(&sourceChartArgs.source, "source", sourceChartArgs.source.Description()) + createSourceChartCmd.Flags().StringVar(&sourceChartArgs.reconcileStrategy, "reconcile-strategy", "ChartVersion", "the reconcile strategy for helm chart (accepted values: Revision and ChartRevision)") + createSourceChartCmd.Flags().Var(&sourceChartArgs.verifyProvider, "verify-provider", sourceOCIRepositoryArgs.verifyProvider.Description()) + createSourceChartCmd.Flags().StringVar(&sourceChartArgs.verifySecretRef, "verify-secret-ref", "", "the name of a secret to use for signature verification") + createSourceChartCmd.Flags().StringVar(&sourceChartArgs.verifySubject, "verify-subject", "", "regular expression to use for the OIDC subject during signature verification") + createSourceChartCmd.Flags().StringVar(&sourceChartArgs.verifyOIDCIssuer, "verify-issuer", "", "regular expression to use for the OIDC issuer during signature verification") + + createSourceCmd.AddCommand(createSourceChartCmd) +} + +func createSourceChartCmdRun(cmd *cobra.Command, args []string) error { + name := args[0] + + if sourceChartArgs.source.Kind == "" || sourceChartArgs.source.Name == "" { + return fmt.Errorf("chart source is required") + } + + if sourceChartArgs.chart == "" { + return fmt.Errorf("chart name or path is required") + } + + logger.Generatef("generating HelmChart source") + + sourceLabels, err := parseLabels() + if err != nil { + return err + } + + helmChart := &sourcev1.HelmChart{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: *kubeconfigArgs.Namespace, + Labels: sourceLabels, + }, + Spec: sourcev1.HelmChartSpec{ + Chart: sourceChartArgs.chart, + Version: sourceChartArgs.chartVersion, + Interval: metav1.Duration{ + Duration: createArgs.interval, + }, + ReconcileStrategy: sourceChartArgs.reconcileStrategy, + SourceRef: sourcev1.LocalHelmChartSourceReference{ + Kind: sourceChartArgs.source.Kind, + Name: sourceChartArgs.source.Name, + }, + }, + } + + if provider := sourceChartArgs.verifyProvider.String(); provider != "" { + helmChart.Spec.Verify = &sourcev1.OCIRepositoryVerification{ + Provider: provider, + } + if secretName := sourceChartArgs.verifySecretRef; secretName != "" { + helmChart.Spec.Verify.SecretRef = &meta.LocalObjectReference{ + Name: secretName, + } + } + verifyIssuer := sourceChartArgs.verifyOIDCIssuer + verifySubject := sourceChartArgs.verifySubject + if verifyIssuer != "" || verifySubject != "" { + helmChart.Spec.Verify.MatchOIDCIdentity = []sourcev1.OIDCIdentityMatch{{ + Issuer: verifyIssuer, + Subject: verifySubject, + }} + } + } else if sourceChartArgs.verifySecretRef != "" { + return fmt.Errorf("a verification provider must be specified when a secret is specified") + } else if sourceChartArgs.verifyOIDCIssuer != "" || sourceOCIRepositoryArgs.verifySubject != "" { + return fmt.Errorf("a verification provider must be specified when OIDC issuer/subject is specified") + } + + if createArgs.export { + return printExport(exportHelmChart(helmChart)) + } + + logger.Actionf("applying HelmChart source") + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) + if err != nil { + return err + } + + namespacedName, err := upsertHelmChart(ctx, kubeClient, helmChart) + if err != nil { + return err + } + + logger.Waitingf("waiting for HelmChart source reconciliation") + readyConditionFunc := isObjectReadyConditionFunc(kubeClient, namespacedName, helmChart) + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, readyConditionFunc); err != nil { + return err + } + logger.Successf("HelmChart source reconciliation completed") + + if helmChart.Status.Artifact == nil { + return fmt.Errorf("HelmChart source reconciliation completed but no artifact was found") + } + logger.Successf("fetched revision: %s", helmChart.Status.Artifact.Revision) + return nil +} + +func upsertHelmChart(ctx context.Context, kubeClient client.Client, + helmChart *sourcev1.HelmChart) (types.NamespacedName, error) { + namespacedName := types.NamespacedName{ + Namespace: helmChart.GetNamespace(), + Name: helmChart.GetName(), + } + + var existing sourcev1.HelmChart + err := kubeClient.Get(ctx, namespacedName, &existing) + if err != nil { + if errors.IsNotFound(err) { + if err := kubeClient.Create(ctx, helmChart); err != nil { + return namespacedName, err + } else { + logger.Successf("source created") + return namespacedName, nil + } + } + return namespacedName, err + } + + existing.Labels = helmChart.Labels + existing.Spec = helmChart.Spec + if err := kubeClient.Update(ctx, &existing); err != nil { + return namespacedName, err + } + helmChart = &existing + logger.Successf("source updated") + return namespacedName, nil +} diff --git a/cmd/flux/create_source_chart_test.go b/cmd/flux/create_source_chart_test.go new file mode 100644 index 0000000000..9566708636 --- /dev/null +++ b/cmd/flux/create_source_chart_test.go @@ -0,0 +1,91 @@ +//go:build unit +// +build unit + +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import "testing" + +func TestCreateSourceChart(t *testing.T) { + tmpl := map[string]string{ + "fluxns": allocateNamespace("flux-system"), + } + setupSourceChart(t, tmpl) + + tests := []struct { + name string + args string + assert assertFunc + }{ + { + name: "missing name", + args: "create source chart --export", + assert: assertError("name is required"), + }, + { + name: "missing source reference", + args: "create source chart podinfo --export ", + assert: assertError("chart source is required"), + }, + { + name: "missing chart name", + args: "create source chart podinfo --source helmrepository/podinfo --export", + assert: assertError("chart name or path is required"), + }, + { + name: "unknown source kind", + args: "create source chart podinfo --source foobar/podinfo --export", + assert: assertError(`invalid argument "foobar/podinfo" for "--source" flag: source kind 'foobar' is not supported, must be one of: HelmRepository, GitRepository, Bucket`), + }, + { + name: "basic chart", + args: "create source chart podinfo --source helmrepository/podinfo --chart podinfo --export", + assert: assertGoldenTemplateFile("testdata/create_source_chart/basic.yaml", tmpl), + }, + { + name: "chart with basic signature verification", + args: "create source chart podinfo --source helmrepository/podinfo --chart podinfo --verify-provider cosign --export", + assert: assertGoldenTemplateFile("testdata/create_source_chart/verify_basic.yaml", tmpl), + }, + { + name: "unknown signature verification provider", + args: "create source chart podinfo --source helmrepository/podinfo --chart podinfo --verify-provider foobar --export", + assert: assertError(`invalid argument "foobar" for "--verify-provider" flag: source OCI verify provider 'foobar' is not supported, must be one of: cosign`), + }, + { + name: "chart with complete signature verification", + args: "create source chart podinfo --source helmrepository/podinfo --chart podinfo --verify-provider cosign --verify-issuer foo --verify-subject bar --export", + assert: assertGoldenTemplateFile("testdata/create_source_chart/verify_complete.yaml", tmpl), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := cmdTestCase{ + args: tt.args + " -n " + tmpl["fluxns"], + assert: tt.assert, + } + cmd.runTestCmd(t) + }) + } +} + +func setupSourceChart(t *testing.T, tmpl map[string]string) { + t.Helper() + testEnv.CreateObjectFile("./testdata/create_source_chart/setup-source.yaml", tmpl, t) +} diff --git a/cmd/flux/create_source_git.go b/cmd/flux/create_source_git.go index 2c6cc59a27..67b03dabec 100644 --- a/cmd/flux/create_source_git.go +++ b/cmd/flux/create_source_git.go @@ -35,7 +35,6 @@ import ( "sigs.k8s.io/yaml" "github.com/fluxcd/pkg/apis/meta" - "github.com/fluxcd/pkg/runtime/conditions" sourcev1 "github.com/fluxcd/source-controller/api/v1" @@ -325,8 +324,8 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error { } logger.Waitingf("waiting for GitRepository source reconciliation") - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isGitRepositoryReady(ctx, kubeClient, namespacedName, &gitRepository)); err != nil { + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, + isObjectReadyConditionFunc(kubeClient, namespacedName, &gitRepository)); err != nil { return err } logger.Successf("GitRepository source reconciliation completed") @@ -368,30 +367,3 @@ func upsertGitRepository(ctx context.Context, kubeClient client.Client, logger.Successf("GitRepository source updated") return namespacedName, nil } - -func isGitRepositoryReady(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, gitRepository *sourcev1.GitRepository) wait.ConditionFunc { - return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, gitRepository) - if err != nil { - return false, err - } - - if c := conditions.Get(gitRepository, meta.ReadyCondition); c != nil { - // Confirm the Ready condition we are observing is for the - // current generation - if c.ObservedGeneration != gitRepository.GetGeneration() { - return false, nil - } - - // Further check the Status - switch c.Status { - case metav1.ConditionTrue: - return true, nil - case metav1.ConditionFalse: - return false, fmt.Errorf(c.Message) - } - } - return false, nil - } -} diff --git a/cmd/flux/create_source_git_test.go b/cmd/flux/create_source_git_test.go index 273659903d..b34e420893 100644 --- a/cmd/flux/create_source_git_test.go +++ b/cmd/flux/create_source_git_test.go @@ -181,12 +181,21 @@ func TestCreateSourceGit(t *testing.T) { Time: time.Now(), }, } + repo.Status.ObservedGeneration = repo.GetGeneration() }, }, { "Failed", command, assertError("failed message"), func(repo *sourcev1.GitRepository) { + stalledCondition := metav1.Condition{ + Type: meta.StalledCondition, + Status: metav1.ConditionTrue, + Reason: sourcev1.URLInvalidReason, + Message: "failed message", + ObservedGeneration: repo.GetGeneration(), + } + apimeta.SetStatusCondition(&repo.Status.Conditions, stalledCondition) newCondition := metav1.Condition{ Type: meta.ReadyCondition, Status: metav1.ConditionFalse, @@ -195,6 +204,7 @@ func TestCreateSourceGit(t *testing.T) { ObservedGeneration: repo.GetGeneration(), } apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition) + repo.Status.ObservedGeneration = repo.GetGeneration() }, }, { "NoArtifact", @@ -210,6 +220,7 @@ func TestCreateSourceGit(t *testing.T) { ObservedGeneration: repo.GetGeneration(), } apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition) + repo.Status.ObservedGeneration = repo.GetGeneration() }, }, } diff --git a/cmd/flux/create_source_helm.go b/cmd/flux/create_source_helm.go index e7fa876b77..d299c43b01 100644 --- a/cmd/flux/create_source_helm.go +++ b/cmd/flux/create_source_helm.go @@ -22,8 +22,6 @@ import ( "net/url" "os" - "github.com/fluxcd/pkg/apis/meta" - "github.com/fluxcd/pkg/runtime/conditions" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -33,7 +31,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1" "github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" @@ -42,8 +41,8 @@ import ( var createSourceHelmCmd = &cobra.Command{ Use: "helm [name]", Short: "Create or update a HelmRepository source", - Long: withPreviewNote(`The create source helm command generates a HelmRepository resource and waits for it to fetch the index. -For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`), + Long: `The create source helm command generates a HelmRepository resource and waits for it to fetch the index. +For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`, Example: ` # Create a source for an HTTPS public Helm repository flux create source helm podinfo \ --url=https://stefanprodan.github.io/podinfo \ @@ -231,8 +230,12 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error { } logger.Waitingf("waiting for HelmRepository source reconciliation") - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isHelmRepositoryReady(ctx, kubeClient, namespacedName, helmRepository)); err != nil { + readyConditionFunc := isObjectReadyConditionFunc(kubeClient, namespacedName, helmRepository) + if helmRepository.Spec.Type == sourcev1.HelmRepositoryTypeOCI { + // HelmRepository type OCI is a static object. + readyConditionFunc = isStaticObjectReadyConditionFunc(kubeClient, namespacedName, helmRepository) + } + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, readyConditionFunc); err != nil { return err } logger.Successf("HelmRepository source reconciliation completed") @@ -279,30 +282,3 @@ func upsertHelmRepository(ctx context.Context, kubeClient client.Client, logger.Successf("source updated") return namespacedName, nil } - -func isHelmRepositoryReady(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, helmRepository *sourcev1.HelmRepository) wait.ConditionFunc { - return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, helmRepository) - if err != nil { - return false, err - } - - if c := conditions.Get(helmRepository, meta.ReadyCondition); c != nil { - // Confirm the Ready condition we are observing is for the - // current generation - if c.ObservedGeneration != helmRepository.GetGeneration() { - return false, nil - } - - // Further check the Status - switch c.Status { - case metav1.ConditionTrue: - return true, nil - case metav1.ConditionFalse: - return false, fmt.Errorf(c.Message) - } - } - return false, nil - } -} diff --git a/cmd/flux/create_source_oci.go b/cmd/flux/create_source_oci.go index 5d6a5f95f2..4713334a57 100644 --- a/cmd/flux/create_source_oci.go +++ b/cmd/flux/create_source_oci.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/fluxcd/pkg/apis/meta" - "github.com/fluxcd/pkg/runtime/conditions" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" "github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/utils" @@ -44,30 +44,43 @@ var createSourceOCIRepositoryCmd = &cobra.Command{ Example: ` # Create an OCIRepository for a public container image flux create source oci podinfo \ --url=oci://ghcr.io/stefanprodan/manifests/podinfo \ - --tag=6.1.6 \ + --tag=6.6.2 \ --interval=10m + + # Create an OCIRepository with OIDC signature verification + flux create source oci podinfo \ + --url=oci://ghcr.io/stefanprodan/manifests/podinfo \ + --tag=6.6.2 \ + --interval=10m \ + --verify-provider=cosign \ + --verify-subject="^https://github.com/stefanprodan/podinfo/.github/workflows/release.yml@refs/tags/6.6.2$" \ + --verify-issuer="^https://token.actions.githubusercontent.com$" `, RunE: createSourceOCIRepositoryCmdRun, } type sourceOCIRepositoryFlags struct { - url string - tag string - semver string - digest string - secretRef string - serviceAccount string - certSecretRef string - ignorePaths []string - provider flags.SourceOCIProvider - insecure bool + url string + tag string + semver string + digest string + secretRef string + serviceAccount string + certSecretRef string + verifyProvider flags.SourceOCIVerifyProvider + verifySecretRef string + verifyOIDCIssuer string + verifySubject string + ignorePaths []string + provider flags.SourceOCIProvider + insecure bool } var sourceOCIRepositoryArgs = newSourceOCIFlags() func newSourceOCIFlags() sourceOCIRepositoryFlags { return sourceOCIRepositoryFlags{ - provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider), + provider: flags.SourceOCIProvider(sourcev1b2.GenericOCIProvider), } } @@ -80,6 +93,10 @@ func init() { createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')") createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.serviceAccount, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret") createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates") + createSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.verifyProvider, "verify-provider", sourceOCIRepositoryArgs.verifyProvider.Description()) + createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifySecretRef, "verify-secret-ref", "", "the name of a secret to use for signature verification") + createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifySubject, "verify-subject", "", "regular expression to use for the OIDC subject during signature verification") + createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifyOIDCIssuer, "verify-issuer", "", "regular expression to use for the OIDC issuer during signature verification") createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)") createSourceOCIRepositoryCmd.Flags().BoolVar(&sourceOCIRepositoryArgs.insecure, "insecure", false, "for when connecting to a non-TLS registries over plain HTTP") @@ -108,20 +125,20 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error { ignorePaths = &ignorePathsStr } - repository := &sourcev1.OCIRepository{ + repository := &sourcev1b2.OCIRepository{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: *kubeconfigArgs.Namespace, Labels: sourceLabels, }, - Spec: sourcev1.OCIRepositorySpec{ + Spec: sourcev1b2.OCIRepositorySpec{ Provider: sourceOCIRepositoryArgs.provider.String(), URL: sourceOCIRepositoryArgs.url, Insecure: sourceOCIRepositoryArgs.insecure, Interval: metav1.Duration{ Duration: createArgs.interval, }, - Reference: &sourcev1.OCIRepositoryRef{}, + Reference: &sourcev1b2.OCIRepositoryRef{}, Ignore: ignorePaths, }, } @@ -156,6 +173,29 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error { } } + if provider := sourceOCIRepositoryArgs.verifyProvider.String(); provider != "" { + repository.Spec.Verify = &sourcev1.OCIRepositoryVerification{ + Provider: provider, + } + if secretName := sourceOCIRepositoryArgs.verifySecretRef; secretName != "" { + repository.Spec.Verify.SecretRef = &meta.LocalObjectReference{ + Name: secretName, + } + } + verifyIssuer := sourceOCIRepositoryArgs.verifyOIDCIssuer + verifySubject := sourceOCIRepositoryArgs.verifySubject + if verifyIssuer != "" || verifySubject != "" { + repository.Spec.Verify.MatchOIDCIdentity = []sourcev1.OIDCIdentityMatch{{ + Issuer: verifyIssuer, + Subject: verifySubject, + }} + } + } else if sourceOCIRepositoryArgs.verifySecretRef != "" { + return fmt.Errorf("a verification provider must be specified when a secret is specified") + } else if sourceOCIRepositoryArgs.verifyOIDCIssuer != "" || sourceOCIRepositoryArgs.verifySubject != "" { + return fmt.Errorf("a verification provider must be specified when OIDC issuer/subject is specified") + } + if createArgs.export { return printExport(exportOCIRepository(repository)) } @@ -175,8 +215,8 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error { } logger.Waitingf("waiting for OCIRepository reconciliation") - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isOCIRepositoryReady(ctx, kubeClient, namespacedName, repository)); err != nil { + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, + isObjectReadyConditionFunc(kubeClient, namespacedName, repository)); err != nil { return err } logger.Successf("OCIRepository reconciliation completed") @@ -189,13 +229,13 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error { } func upsertOCIRepository(ctx context.Context, kubeClient client.Client, - ociRepository *sourcev1.OCIRepository) (types.NamespacedName, error) { + ociRepository *sourcev1b2.OCIRepository) (types.NamespacedName, error) { namespacedName := types.NamespacedName{ Namespace: ociRepository.GetNamespace(), Name: ociRepository.GetName(), } - var existing sourcev1.OCIRepository + var existing sourcev1b2.OCIRepository err := kubeClient.Get(ctx, namespacedName, &existing) if err != nil { if errors.IsNotFound(err) { @@ -218,30 +258,3 @@ func upsertOCIRepository(ctx context.Context, kubeClient client.Client, logger.Successf("OCIRepository updated") return namespacedName, nil } - -func isOCIRepositoryReady(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, ociRepository *sourcev1.OCIRepository) wait.ConditionFunc { - return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, ociRepository) - if err != nil { - return false, err - } - - if c := conditions.Get(ociRepository, meta.ReadyCondition); c != nil { - // Confirm the Ready condition we are observing is for the - // current generation - if c.ObservedGeneration != ociRepository.GetGeneration() { - return false, nil - } - - // Further check the Status - switch c.Status { - case metav1.ConditionTrue: - return true, nil - case metav1.ConditionFalse: - return false, fmt.Errorf(c.Message) - } - } - return false, nil - } -} diff --git a/cmd/flux/create_source_oci_test.go b/cmd/flux/create_source_oci_test.go index 8972b8131c..da08d9f640 100644 --- a/cmd/flux/create_source_oci_test.go +++ b/cmd/flux/create_source_oci_test.go @@ -36,6 +36,36 @@ func TestCreateSourceOCI(t *testing.T) { args: "create source oci podinfo", assertFunc: assertError("url is required"), }, + { + name: "verify secret specified but provider missing", + args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-secret-ref=cosign-pub", + assertFunc: assertError("a verification provider must be specified when a secret is specified"), + }, + { + name: "verify issuer specified but provider missing", + args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-issuer=github.com", + assertFunc: assertError("a verification provider must be specified when OIDC issuer/subject is specified"), + }, + { + name: "verify identity specified but provider missing", + args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-subject=developer", + assertFunc: assertError("a verification provider must be specified when OIDC issuer/subject is specified"), + }, + { + name: "verify issuer specified but subject missing", + args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-issuer=github --verify-provider=cosign --export", + assertFunc: assertGoldenFile("./testdata/oci/export_with_issuer.golden"), + }, + { + name: "all verify fields set", + args: "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", + assertFunc: assertGoldenFile("./testdata/oci/export_with_issuer.golden"), + }, + { + name: "verify subject specified but issuer missing", + args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-subject=stefanprodan --verify-provider=cosign --export", + assertFunc: assertGoldenFile("./testdata/oci/export_with_subject.golden"), + }, { name: "export manifest", args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --export", @@ -46,6 +76,11 @@ func TestCreateSourceOCI(t *testing.T) { args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --secret-ref=creds --export", assertFunc: assertGoldenFile("./testdata/oci/export_with_secret.golden"), }, + { + name: "export manifest with verify secret", + args: "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", + assertFunc: assertGoldenFile("./testdata/oci/export_with_verify_secret.golden"), + }, } for _, tt := range tests { diff --git a/cmd/flux/delete_alert.go b/cmd/flux/delete_alert.go index f0c8ee58dc..cda9f04d2a 100644 --- a/cmd/flux/delete_alert.go +++ b/cmd/flux/delete_alert.go @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" ) var deleteAlertCmd = &cobra.Command{ diff --git a/cmd/flux/delete_alertprovider.go b/cmd/flux/delete_alertprovider.go index 871f42a65d..73a54b2a1b 100644 --- a/cmd/flux/delete_alertprovider.go +++ b/cmd/flux/delete_alertprovider.go @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" ) var deleteAlertProviderCmd = &cobra.Command{ diff --git a/cmd/flux/delete_helmrelease.go b/cmd/flux/delete_helmrelease.go index 6f2300c502..d2cfd67583 100644 --- a/cmd/flux/delete_helmrelease.go +++ b/cmd/flux/delete_helmrelease.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Flux authors +Copyright 2024 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,14 +19,14 @@ package main import ( "github.com/spf13/cobra" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" ) var deleteHelmReleaseCmd = &cobra.Command{ Use: "helmrelease [name]", Aliases: []string{"hr"}, Short: "Delete a HelmRelease resource", - Long: withPreviewNote("The delete helmrelease command removes the given HelmRelease from the cluster."), + Long: "The delete helmrelease command removes the given HelmRelease from the cluster.", Example: ` # Delete a Helm release and the Kubernetes resources created by it flux delete hr podinfo`, ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)), diff --git a/cmd/flux/delete_image_update.go b/cmd/flux/delete_image_update.go index 0c59ff892e..5470c3039b 100644 --- a/cmd/flux/delete_image_update.go +++ b/cmd/flux/delete_image_update.go @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" ) var deleteImageUpdateCmd = &cobra.Command{ diff --git a/cmd/flux/delete_source_chart.go b/cmd/flux/delete_source_chart.go new file mode 100644 index 0000000000..db8f20907f --- /dev/null +++ b/cmd/flux/delete_source_chart.go @@ -0,0 +1,40 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + + sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" +) + +var deleteSourceChartCmd = &cobra.Command{ + Use: "chart [name]", + Short: "Delete a HelmChart source", + Long: "The delete source chart command deletes the given HelmChart from the cluster.", + Example: ` # Delete a HelmChart + flux delete source chart podinfo`, + ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)), + RunE: deleteCommand{ + apiType: helmChartType, + object: universalAdapter{&sourcev1.HelmChart{}}, + }.run, +} + +func init() { + deleteSourceCmd.AddCommand(deleteSourceChartCmd) +} diff --git a/cmd/flux/delete_source_helm.go b/cmd/flux/delete_source_helm.go index bd057e8aaa..b4860fbbff 100644 --- a/cmd/flux/delete_source_helm.go +++ b/cmd/flux/delete_source_helm.go @@ -19,13 +19,13 @@ package main import ( "github.com/spf13/cobra" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" ) var deleteSourceHelmCmd = &cobra.Command{ Use: "helm [name]", Short: "Delete a HelmRepository source", - Long: withPreviewNote("The delete source helm command deletes the given HelmRepository from the cluster."), + Long: "The delete source helm command deletes the given HelmRepository from the cluster.", Example: ` # Delete a Helm repository flux delete source helm podinfo`, ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)), diff --git a/cmd/flux/diff_kustomization.go b/cmd/flux/diff_kustomization.go index 0c4968ebbc..0b85a2b1df 100644 --- a/cmd/flux/diff_kustomization.go +++ b/cmd/flux/diff_kustomization.go @@ -23,8 +23,9 @@ import ( "github.com/spf13/cobra" - "github.com/fluxcd/flux2/v2/internal/build" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + + "github.com/fluxcd/flux2/v2/internal/build" ) var diffKsCmd = &cobra.Command{ @@ -53,6 +54,7 @@ type diffKsFlags struct { path string ignorePaths []string progressBar bool + strictSubst bool } var diffKsArgs diffKsFlags @@ -62,6 +64,8 @@ func init() { diffKsCmd.Flags().BoolVar(&diffKsArgs.progressBar, "progress-bar", true, "Boolean to set the progress bar. The default value is true.") diffKsCmd.Flags().StringSliceVar(&diffKsArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in .gitignore format") diffKsCmd.Flags().StringVar(&diffKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.") + diffKsCmd.Flags().BoolVar(&diffKsArgs.strictSubst, "strict-substitute", false, + "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.") diffCmd.AddCommand(diffKsCmd) } @@ -96,6 +100,7 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error { build.WithKustomizationFile(diffKsArgs.kustomizationFile), build.WithProgressBar(), build.WithIgnore(diffKsArgs.ignorePaths), + build.WithStrictSubstitute(diffKsArgs.strictSubst), ) } else { builder, err = build.NewBuilder(name, diffKsArgs.path, @@ -103,6 +108,7 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error { build.WithTimeout(rootArgs.timeout), build.WithKustomizationFile(diffKsArgs.kustomizationFile), build.WithIgnore(diffKsArgs.ignorePaths), + build.WithStrictSubstitute(diffKsArgs.strictSubst), ) } diff --git a/cmd/flux/diff_kustomization_test.go b/cmd/flux/diff_kustomization_test.go index fc2a9c67ce..a381a61f0a 100644 --- a/cmd/flux/diff_kustomization_test.go +++ b/cmd/flux/diff_kustomization_test.go @@ -91,6 +91,12 @@ func TestDiffKustomization(t *testing.T) { objectFile: "./testdata/diff-kustomization/stringdata-sops-secret.yaml", assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-stringdata-sops-secret.golden"), }, + { + name: "diff where kustomization file has multiple objects with the same name", + args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false --kustomization-file ./testdata/diff-kustomization/flux-kustomization-multiobj.yaml", + objectFile: "", + assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"), + }, } tmpl := map[string]string{ diff --git a/cmd/flux/envsubst.go b/cmd/flux/envsubst.go new file mode 100644 index 0000000000..96ddefa792 --- /dev/null +++ b/cmd/flux/envsubst.go @@ -0,0 +1,74 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bufio" + "fmt" + + "github.com/fluxcd/pkg/envsubst" + "github.com/spf13/cobra" +) + +var envsubstCmd = &cobra.Command{ + Use: "envsubst", + Args: cobra.NoArgs, + Short: "envsubst substitutes the values of environment variables", + Long: withPreviewNote(`The envsubst command substitutes the values of environment variables +in the string piped as standard input and writes the result to the standard output. This command can be used +to replicate the behavior of the Flux Kustomization post-build substitutions.`), + Example: ` # Run env var substitutions on the kustomization build output + export cluster_region=eu-central-1 + kustomize build . | flux envsubst + + # Run env var substitutions and error out if a variable is not set + kustomize build . | flux envsubst --strict +`, + RunE: runEnvsubstCmd, +} + +type envsubstFlags struct { + strict bool +} + +var envsubstArgs envsubstFlags + +func init() { + envsubstCmd.Flags().BoolVar(&envsubstArgs.strict, "strict", false, + "fail if a variable without a default value is declared in the input but is missing from the environment") + rootCmd.AddCommand(envsubstCmd) +} + +func runEnvsubstCmd(cmd *cobra.Command, args []string) error { + stdin := bufio.NewScanner(rootCmd.InOrStdin()) + stdout := bufio.NewWriter(rootCmd.OutOrStdout()) + for stdin.Scan() { + line, err := envsubst.EvalEnv(stdin.Text(), envsubstArgs.strict) + if err != nil { + return err + } + _, err = fmt.Fprintln(stdout, line) + if err != nil { + return err + } + err = stdout.Flush() + if err != nil { + return err + } + } + return nil +} diff --git a/cmd/flux/envsubst_test.go b/cmd/flux/envsubst_test.go new file mode 100644 index 0000000000..38010b5f43 --- /dev/null +++ b/cmd/flux/envsubst_test.go @@ -0,0 +1,50 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bytes" + "os" + "testing" + + . "github.com/onsi/gomega" +) + +func TestEnvsubst(t *testing.T) { + g := NewWithT(t) + input, err := os.ReadFile("testdata/envsubst/file.yaml") + g.Expect(err).NotTo(HaveOccurred()) + + t.Setenv("REPO_NAME", "test") + + output, err := executeCommandWithIn("envsubst", bytes.NewReader(input)) + g.Expect(err).NotTo(HaveOccurred()) + + expected, err := os.ReadFile("testdata/envsubst/file.gold") + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(output).To(Equal(string(expected))) +} + +func TestEnvsubst_Strinct(t *testing.T) { + g := NewWithT(t) + input, err := os.ReadFile("testdata/envsubst/file.yaml") + g.Expect(err).NotTo(HaveOccurred()) + + _, err = executeCommandWithIn("envsubst --strict", bytes.NewReader(input)) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("variable not set (strict mode)")) +} diff --git a/cmd/flux/events.go b/cmd/flux/events.go index ddfd2a3940..5ac5afd232 100644 --- a/cmd/flux/events.go +++ b/cmd/flux/events.go @@ -39,12 +39,12 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "sigs.k8s.io/controller-runtime/pkg/client" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" notificationv1 "github.com/fluxcd/notification-controller/api/v1" - notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3" sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" @@ -62,8 +62,14 @@ var eventsCmd = &cobra.Command{ # Display events for flux resources in all namespaces flux events -A - # Display events for flux resources + # Display events for a Kustomization named podinfo flux events --for Kustomization/podinfo + + # Display events for all Kustomizations in default namespace + flux events --for Kustomization -n default + + # Display warning events for alert resources + flux events --for Alert/podinfo --types warning `, RunE: eventsCmdRun, } @@ -84,7 +90,7 @@ func init() { "indicate if the events should be streamed") eventsCmd.Flags().StringVar(&eventArgs.forSelector, "for", "", "get events for a particular object") - eventsCmd.Flags().StringSliceVar(&eventArgs.filterTypes, "types", []string{}, "filter events for certain types") + eventsCmd.Flags().StringSliceVar(&eventArgs.filterTypes, "types", []string{}, "filter events for certain types (valid types are: Normal, Warning)") rootCmd.AddCommand(eventsCmd) } @@ -92,6 +98,10 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error { ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) defer cancel() + if err := validateEventTypes(eventArgs.filterTypes); err != nil { + return err + } + kubeclient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) if err != nil { return err @@ -103,21 +113,33 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error { } var diffRefNs bool - clientListOpts := getListOpt(namespace, eventArgs.forSelector) + clientListOpts := []client.ListOption{client.InNamespace(*kubeconfigArgs.Namespace)} var refListOpts [][]client.ListOption if eventArgs.forSelector != "" { - refs, err := getObjectRef(ctx, kubeclient, eventArgs.forSelector, *kubeconfigArgs.Namespace) + kind, name := getKindNameFromSelector(eventArgs.forSelector) + if kind == "" { + return fmt.Errorf("--for selector must be of format [/]") + } + + refInfoKind, err := fluxKindMap.getRefInfo(kind) if err != nil { return err } + clientListOpts = append(clientListOpts, getListOpt(refInfoKind.gvk.Kind, name)) + if name != "" { + refs, err := getObjectRef(ctx, kubeclient, refInfoKind, name, *kubeconfigArgs.Namespace) + if err != nil { + return err + } - for _, ref := range refs { - kind, name, refNs := utils.ParseObjectKindNameNamespace(ref) - if refNs != namespace { - diffRefNs = true + for _, ref := range refs { + refKind, refName, refNs := utils.ParseObjectKindNameNamespace(ref) + if refNs != namespace { + diffRefNs = true + } + refOpt := []client.ListOption{getListOpt(refKind, refName), client.InNamespace(refNs)} + refListOpts = append(refListOpts, refOpt) } - refSelector := fmt.Sprintf("%s/%s", kind, name) - refListOpts = append(refListOpts, getListOpt(refNs, refSelector)) } } @@ -127,6 +149,9 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error { } rows, err := getRows(ctx, kubeclient, clientListOpts, refListOpts, showNamespace) + if err != nil { + return err + } if len(rows) == 0 { if eventArgs.allNamespaces { logger.Failuref("No events found.") @@ -137,8 +162,7 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error { return nil } headers := getHeaders(showNamespace) - err = printers.TablePrinter(headers).Print(cmd.OutOrStdout(), rows) - return err + return printers.TablePrinter(headers).Print(cmd.OutOrStdout(), rows) } func getRows(ctx context.Context, kubeclient client.Client, clientListOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) ([][]string, error) { @@ -168,11 +192,11 @@ func getRows(ctx context.Context, kubeclient client.Client, clientListOpts []cli func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.EventList, clientListOpts []client.ListOption) error { listOpts := &metav1.ListOptions{} + clientListOpts = append(clientListOpts, client.Limit(cmdutil.DefaultChunkSize)) err := runtimeresource.FollowContinue(listOpts, func(options metav1.ListOptions) (runtime.Object, error) { newEvents := &corev1.EventList{} - err := kubeclient.List(ctx, newEvents, clientListOpts...) - if err != nil { + if err := kubeclient.List(ctx, newEvents, clientListOpts...); err != nil { return nil, fmt.Errorf("error getting events: %w", err) } el.Items = append(el.Items, newEvents.Items...) @@ -182,21 +206,22 @@ func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.E return err } -func getListOpt(namespace, selector string) []client.ListOption { - clientListOpts := []client.ListOption{client.Limit(cmdutil.DefaultChunkSize), client.InNamespace(namespace)} - if selector != "" { - kind, name := utils.ParseObjectKindName(selector) - sel := fields.AndSelectors( +func getListOpt(kind, name string) client.ListOption { + var sel fields.Selector + if name == "" { + sel = fields.OneTermEqualSelector("involvedObject.kind", kind) + } else { + sel = fields.AndSelectors( fields.OneTermEqualSelector("involvedObject.kind", kind), fields.OneTermEqualSelector("involvedObject.name", name)) - clientListOpts = append(clientListOpts, client.MatchingFieldsSelector{Selector: sel}) } - return clientListOpts + return client.MatchingFieldsSelector{Selector: sel} } func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) error { event := &corev1.EventList{} + listOpts = append(listOpts, client.Limit(cmdutil.DefaultChunkSize)) eventWatch, err := kubeclient.Watch(ctx, event, listOpts...) if err != nil { return err @@ -222,12 +247,7 @@ func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpt hdr = getHeaders(showNs) firstIteration = false } - err = printers.TablePrinter(hdr).Print(os.Stdout, [][]string{rows}) - if err != nil { - return err - } - - return nil + return printers.TablePrinter(hdr).Print(os.Stdout, [][]string{rows}) } for _, refOpts := range refListOpts { @@ -236,8 +256,7 @@ func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpt return err } go func() { - err := receiveEventChan(ctx, refEventWatch, handleEvent) - if err != nil { + if err := receiveEventChan(ctx, refEventWatch, handleEvent); err != nil { logger.Failuref("error watching events: %s", err.Error()) } }() @@ -286,13 +305,7 @@ func getEventRow(e corev1.Event, showNs bool) []string { // getObjectRef is used to get the metadata of a resource that the selector(in the format ) references. // It returns an empty string if the resource doesn't reference any resource // and a string with the format `/.` if it does. -func getObjectRef(ctx context.Context, kubeclient client.Client, selector string, ns string) ([]string, error) { - kind, name := utils.ParseObjectKindName(selector) - ref, err := fluxKindMap.getRefInfo(kind) - if err != nil { - return nil, fmt.Errorf("error getting groupversion: %w", err) - } - +func getObjectRef(ctx context.Context, kubeclient client.Client, ref refInfo, name, ns string) ([]string, error) { // the resource has no source ref if len(ref.field) == 0 { return nil, nil @@ -300,31 +313,30 @@ func getObjectRef(ctx context.Context, kubeclient client.Client, selector string obj := &unstructured.Unstructured{} obj.SetGroupVersionKind(schema.GroupVersionKind{ - Kind: kind, - Version: ref.gv.Version, - Group: ref.gv.Group, + Kind: ref.gvk.Kind, + Version: ref.gvk.Version, + Group: ref.gvk.Group, }) objName := types.NamespacedName{ Namespace: ns, Name: name, } - err = kubeclient.Get(ctx, objName, obj) - if err != nil { + if err := kubeclient.Get(ctx, objName, obj); err != nil { return nil, err } - var ok bool refKind := ref.kind if refKind == "" { kindField := append(ref.field, "kind") - refKind, ok, err = unstructured.NestedString(obj.Object, kindField...) + specKind, ok, err := unstructured.NestedString(obj.Object, kindField...) if err != nil { return nil, err } if !ok { return nil, fmt.Errorf("field '%s' for '%s' not found", strings.Join(kindField, "."), objName) } + refKind = specKind } nameField := append(ref.field, "name") @@ -374,49 +386,71 @@ func (r refMap) hasKind(kind string) bool { return err == nil } +// validateEventTypes checks that the event types passed into the function +// is either equal to `Normal` or `Warning` which are currently the two supported types. +// https://github.com/kubernetes/kubernetes/blob/a8a1abc25cad87333840cd7d54be2efaf31a3177/staging/src/k8s.io/api/core/v1/types.go#L6212 +func validateEventTypes(eventTypes []string) error { + for _, t := range eventTypes { + if !strings.EqualFold(corev1.EventTypeWarning, t) && !strings.EqualFold(corev1.EventTypeNormal, t) { + return fmt.Errorf("type '%s' not supported. Supported types are Normal, Warning", t) + } + } + + return nil +} + type refInfo struct { - gv schema.GroupVersion - kind string + // gvk is the group version kind of the resource + gvk schema.GroupVersionKind + // kind is the kind that the resource references if it's not static + kind string + // crossNamespaced indicates if this resource uses cross namespaced references crossNamespaced bool - otherRefs func(namespace, name string) []string - field []string + // otherRefs returns other reference that might not be directly accessible + // from the spec of the object + otherRefs func(namespace, name string) []string + field []string } var fluxKindMap = refMap{ kustomizev1.KustomizationKind: { - gv: kustomizev1.GroupVersion, + gvk: kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind), crossNamespaced: true, field: []string{"spec", "sourceRef"}, }, helmv2.HelmReleaseKind: { - gv: helmv2.GroupVersion, + gvk: helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind), crossNamespaced: true, otherRefs: func(namespace, name string) []string { - return []string{fmt.Sprintf("%s/%s-%s", sourcev1b2.HelmChartKind, namespace, name)} + return []string{fmt.Sprintf("%s/%s-%s", sourcev1.HelmChartKind, namespace, name)} }, field: []string{"spec", "chart", "spec", "sourceRef"}, }, - notificationv1b2.AlertKind: { - gv: notificationv1b2.GroupVersion, - kind: notificationv1b2.ProviderKind, + notificationv1b3.AlertKind: { + gvk: notificationv1b3.GroupVersion.WithKind(notificationv1b3.AlertKind), + kind: notificationv1b3.ProviderKind, crossNamespaced: false, field: []string{"spec", "providerRef"}, }, - notificationv1.ReceiverKind: {gv: notificationv1.GroupVersion}, - notificationv1b2.ProviderKind: {gv: notificationv1b2.GroupVersion}, + notificationv1.ReceiverKind: {gvk: notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)}, + notificationv1b3.ProviderKind: {gvk: notificationv1b3.GroupVersion.WithKind(notificationv1b3.ProviderKind)}, imagev1.ImagePolicyKind: { - gv: imagev1.GroupVersion, + gvk: imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind), kind: imagev1.ImageRepositoryKind, crossNamespaced: true, field: []string{"spec", "imageRepositoryRef"}, }, - sourcev1.GitRepositoryKind: {gv: sourcev1.GroupVersion}, - sourcev1b2.OCIRepositoryKind: {gv: sourcev1b2.GroupVersion}, - sourcev1b2.BucketKind: {gv: sourcev1b2.GroupVersion}, - sourcev1b2.HelmRepositoryKind: {gv: sourcev1b2.GroupVersion}, - sourcev1b2.HelmChartKind: {gv: sourcev1b2.GroupVersion}, - autov1.ImageUpdateAutomationKind: {gv: autov1.GroupVersion}, - imagev1.ImageRepositoryKind: {gv: imagev1.GroupVersion}, + sourcev1.HelmChartKind: { + gvk: sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind), + crossNamespaced: true, + field: []string{"spec", "sourceRef"}, + }, + sourcev1.GitRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)}, + sourcev1b2.OCIRepositoryKind: {gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.OCIRepositoryKind)}, + sourcev1b2.BucketKind: {gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.BucketKind)}, + sourcev1.HelmRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)}, + autov1.ImageUpdateAutomationKind: {gvk: autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)}, + imagev1.ImageRepositoryKind: {gvk: imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)}, } func ignoreEvent(e corev1.Event) bool { @@ -434,7 +468,19 @@ func ignoreEvent(e corev1.Event) bool { return false } -// The functions below are copied from: https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/events/events.go#L347 +func getKindNameFromSelector(selector string) (string, string) { + kind, name := utils.ParseObjectKindName(selector) + // if there's no slash in the selector utils.ParseObjectKindName returns the + // input string as the name but here we want it as the kind instead + if kind == "" && name != "" { + kind = name + name = "" + } + + return kind, name +} + +// The functions below are copied from: https://github.com/kubernetes/kubectl/blob/4ecd7bd0f0799f191335a331ca3c6a397a888233/pkg/cmd/events/events.go#L294 // SortableEvents implements sort.Interface for []api.Event by time type SortableEvents []corev1.Event diff --git a/cmd/flux/events_test.go b/cmd/flux/events_test.go index a697743173..151cd55c8d 100644 --- a/cmd/flux/events_test.go +++ b/cmd/flux/events_test.go @@ -27,21 +27,11 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/runtime" - cmdutil "k8s.io/kubectl/pkg/cmd/util" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - helmv2beta1 "github.com/fluxcd/helm-controller/api/v2beta1" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" - imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" - kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" - notificationv1 "github.com/fluxcd/notification-controller/api/v1" - notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2" eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" - "github.com/fluxcd/pkg/ssa" - sourcev1 "github.com/fluxcd/source-controller/api/v1" - sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" + ssautil "github.com/fluxcd/pkg/ssa/utils" "github.com/fluxcd/flux2/v2/internal/utils" ) @@ -88,7 +78,7 @@ spec: timeout: 1m0s url: ssh://git@github.com/example/repo --- -apiVersion: helm.toolkit.fluxcd.io/v2beta1 +apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: podinfo @@ -105,7 +95,7 @@ spec: version: '*' interval: 5m0s --- -apiVersion: source.toolkit.fluxcd.io/v1beta2 +apiVersion: source.toolkit.fluxcd.io/v1 kind: HelmRepository metadata: name: podinfo @@ -114,7 +104,7 @@ spec: interval: 1m0s url: https://stefanprodan.github.io/podinfo --- -apiVersion: source.toolkit.fluxcd.io/v1beta2 +apiVersion: source.toolkit.fluxcd.io/v1 kind: HelmChart metadata: name: default-podinfo @@ -128,7 +118,7 @@ spec: name: podinfo-chart version: '*' --- -apiVersion: notification.toolkit.fluxcd.io/v1beta2 +apiVersion: notification.toolkit.fluxcd.io/v1beta3 kind: Alert metadata: name: webapp @@ -141,7 +131,7 @@ spec: providerRef: name: slack --- -apiVersion: notification.toolkit.fluxcd.io/v1beta2 +apiVersion: notification.toolkit.fluxcd.io/v1beta3 kind: Provider metadata: name: slack @@ -170,10 +160,10 @@ metadata: func Test_getObjectRef(t *testing.T) { g := NewWithT(t) - objs, err := ssa.ReadObjects(strings.NewReader(objects)) + objs, err := ssautil.ReadObjects(strings.NewReader(objects)) g.Expect(err).To(Not(HaveOccurred())) - builder := fake.NewClientBuilder().WithScheme(getScheme()) + builder := fake.NewClientBuilder().WithScheme(utils.NewScheme()) for _, obj := range objs { builder = builder.WithObjects(obj) } @@ -216,6 +206,12 @@ func Test_getObjectRef(t *testing.T) { namespace: "default", want: []string{"ImageRepository/acr-podinfo.flux-system"}, }, + { + name: "Source Ref for ImagePolicy (lowercased)", + selector: "imagepolicy/podinfo", + namespace: "default", + want: []string{"ImageRepository/acr-podinfo.flux-system"}, + }, { name: "Empty Ref for Provider", selector: "Provider/slack", @@ -232,11 +228,13 @@ func Test_getObjectRef(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - got, err := getObjectRef(context.Background(), c, tt.selector, tt.namespace) + kind, name := getKindNameFromSelector(tt.selector) + infoRef, err := fluxKindMap.getRefInfo(kind) if tt.wantErr { g.Expect(err).To(HaveOccurred()) return } + got, err := getObjectRef(context.Background(), c, infoRef, name, tt.namespace) g.Expect(err).To(Not(HaveOccurred())) g.Expect(got).To(Equal(tt.want)) @@ -246,10 +244,10 @@ func Test_getObjectRef(t *testing.T) { func Test_getRows(t *testing.T) { g := NewWithT(t) - objs, err := ssa.ReadObjects(strings.NewReader(objects)) + objs, err := ssautil.ReadObjects(strings.NewReader(objects)) g.Expect(err).To(Not(HaveOccurred())) - builder := fake.NewClientBuilder().WithScheme(getScheme()) + builder := fake.NewClientBuilder().WithScheme(utils.NewScheme()) for _, obj := range objs { builder = builder.WithObjects(obj) } @@ -261,6 +259,7 @@ func Test_getRows(t *testing.T) { } builder = builder.WithLists(eventList) builder.WithIndex(&corev1.Event{}, "involvedObject.kind/name", kindNameIndexer) + builder.WithIndex(&corev1.Event{}, "involvedObject.kind", kindIndexer) c := builder.Build() tests := []struct { @@ -320,6 +319,16 @@ func Test_getRows(t *testing.T) { {"flux-system", "", "info", "Info Reason", "GitRepository/flux-system", "Info Message"}, }, }, + { + name: "All Kustomization (lowercased selector)", + selector: "kustomization", + expected: [][]string{ + {"default", "", "error", "Error Reason", "Kustomization/podinfo", "Error Message"}, + {"default", "", "info", "Info Reason", "Kustomization/podinfo", "Info Message"}, + {"flux-system", "", "error", "Error Reason", "Kustomization/flux-system", "Error Message"}, + {"flux-system", "", "info", "Info Reason", "Kustomization/flux-system", "Info Message"}, + }, + }, { name: "HelmRelease with crossnamespaced HelmRepository", selector: "HelmRelease/podinfo", @@ -333,6 +342,19 @@ func Test_getRows(t *testing.T) { {"flux-system", "", "info", "Info Reason", "HelmChart/default-podinfo", "Info Message"}, }, }, + { + name: "HelmRelease with crossnamespaced HelmRepository (lowercased)", + selector: "helmrelease/podinfo", + namespace: "default", + expected: [][]string{ + {"default", "", "error", "Error Reason", "HelmRelease/podinfo", "Error Message"}, + {"default", "", "info", "Info Reason", "HelmRelease/podinfo", "Info Message"}, + {"flux-system", "", "error", "Error Reason", "HelmRepository/podinfo", "Error Message"}, + {"flux-system", "", "info", "Info Reason", "HelmRepository/podinfo", "Info Message"}, + {"flux-system", "", "error", "Error Reason", "HelmChart/default-podinfo", "Error Message"}, + {"flux-system", "", "info", "Info Reason", "HelmChart/default-podinfo", "Info Message"}, + }, + }, } for _, tt := range tests { @@ -341,59 +363,49 @@ func Test_getRows(t *testing.T) { var refs []string var refNs, refKind, refName string + var clientOpts = []client.ListOption{client.InNamespace(tt.namespace)} if tt.selector != "" { - refs, err = getObjectRef(context.Background(), c, tt.selector, tt.namespace) - g.Expect(err).To(Not(HaveOccurred())) + kind, name := getKindNameFromSelector(tt.selector) + infoRef, err := fluxKindMap.getRefInfo(kind) + clientOpts = append(clientOpts, getTestListOpt(infoRef.gvk.Kind, name)) + if name != "" { + g.Expect(err).To(Not(HaveOccurred())) + refs, err = getObjectRef(context.Background(), c, infoRef, name, tt.namespace) + g.Expect(err).To(Not(HaveOccurred())) + } } g.Expect(err).To(Not(HaveOccurred())) - clientOpts := getTestListOpt(tt.namespace, tt.selector) var refOpts [][]client.ListOption for _, ref := range refs { refKind, refName, refNs = utils.ParseObjectKindNameNamespace(ref) - refSelector := fmt.Sprintf("%s/%s", refKind, refName) - refOpts = append(refOpts, getTestListOpt(refNs, refSelector)) + refOpts = append(refOpts, []client.ListOption{client.InNamespace(refNs), getTestListOpt(refKind, refName)}) } showNs := tt.namespace == "" || (refNs != "" && refNs != tt.namespace) rows, err := getRows(context.Background(), c, clientOpts, refOpts, showNs) g.Expect(err).To(Not(HaveOccurred())) - g.Expect(rows).To(Equal(tt.expected)) + g.Expect(rows).To(ConsistOf(tt.expected)) }) } } -func getTestListOpt(namespace, selector string) []client.ListOption { - clientListOpts := []client.ListOption{client.Limit(cmdutil.DefaultChunkSize), client.InNamespace(namespace)} - if selector != "" { - sel := fields.OneTermEqualSelector("involvedObject.kind/name", selector) - clientListOpts = append(clientListOpts, client.MatchingFieldsSelector{Selector: sel}) +func getTestListOpt(kind, name string) client.ListOption { + var sel fields.Selector + if name == "" { + sel = fields.OneTermEqualSelector("involvedObject.kind", kind) + } else { + sel = fields.OneTermEqualSelector("involvedObject.kind/name", fmt.Sprintf("%s/%s", kind, name)) } - - return clientListOpts -} - -func getScheme() *runtime.Scheme { - newscheme := runtime.NewScheme() - corev1.AddToScheme(newscheme) - kustomizev1.AddToScheme(newscheme) - helmv2beta1.AddToScheme(newscheme) - notificationv1.AddToScheme(newscheme) - notificationv1b2.AddToScheme(newscheme) - imagev1.AddToScheme(newscheme) - autov1.AddToScheme(newscheme) - sourcev1.AddToScheme(newscheme) - sourcev1b2.AddToScheme(newscheme) - - return newscheme + return client.MatchingFieldsSelector{Selector: sel} } func createEvent(obj client.Object, eventType, msg, reason string) corev1.Event { return corev1.Event{ ObjectMeta: metav1.ObjectMeta{ Namespace: obj.GetNamespace(), - // name of event needs to be unique so fak + // name of event needs to be unique Name: obj.GetNamespace() + obj.GetNamespace() + obj.GetObjectKind().GroupVersionKind().Kind + eventType, }, Reason: reason, @@ -415,3 +427,12 @@ func kindNameIndexer(obj client.Object) []string { return []string{fmt.Sprintf("%s/%s", e.InvolvedObject.Kind, e.InvolvedObject.Name)} } + +func kindIndexer(obj client.Object) []string { + e, ok := obj.(*corev1.Event) + if !ok { + panic(fmt.Sprintf("Expected a Event, got %T", e)) + } + + return []string{e.InvolvedObject.Kind} +} diff --git a/cmd/flux/export_alert.go b/cmd/flux/export_alert.go index 63f9f59f1c..2f15871149 100644 --- a/cmd/flux/export_alert.go +++ b/cmd/flux/export_alert.go @@ -20,7 +20,7 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" ) var exportAlertCmd = &cobra.Command{ diff --git a/cmd/flux/export_alertprovider.go b/cmd/flux/export_alertprovider.go index 782648cab4..e4c3ab9aa6 100644 --- a/cmd/flux/export_alertprovider.go +++ b/cmd/flux/export_alertprovider.go @@ -20,7 +20,7 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" ) var exportAlertProviderCmd = &cobra.Command{ diff --git a/cmd/flux/export_helmrelease.go b/cmd/flux/export_helmrelease.go index e00b3494bd..aaa209f54b 100644 --- a/cmd/flux/export_helmrelease.go +++ b/cmd/flux/export_helmrelease.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Flux authors +Copyright 2024 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,14 +20,14 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" ) var exportHelmReleaseCmd = &cobra.Command{ Use: "helmrelease [name]", Aliases: []string{"hr"}, Short: "Export HelmRelease resources in YAML format", - Long: withPreviewNote("The export helmrelease command exports one or all HelmRelease resources in YAML format."), + Long: "The export helmrelease command exports one or all HelmRelease resources in YAML format.", Example: ` # Export all HelmRelease resources flux export helmrelease --all > kustomizations.yaml diff --git a/cmd/flux/export_image_update.go b/cmd/flux/export_image_update.go index 2bbc1700e1..59aae7d955 100644 --- a/cmd/flux/export_image_update.go +++ b/cmd/flux/export_image_update.go @@ -20,7 +20,7 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" ) var exportImageUpdateCmd = &cobra.Command{ diff --git a/cmd/flux/export_secret.go b/cmd/flux/export_secret.go index 4a87e233ea..76d6ec5626 100644 --- a/cmd/flux/export_secret.go +++ b/cmd/flux/export_secret.go @@ -46,7 +46,6 @@ type exportableWithSecretList interface { } type exportWithSecretCommand struct { - apiType object exportableWithSecret list exportableWithSecretList } diff --git a/cmd/flux/export_source_chart.go b/cmd/flux/export_source_chart.go new file mode 100644 index 0000000000..ea9b720773 --- /dev/null +++ b/cmd/flux/export_source_chart.go @@ -0,0 +1,67 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + sourcev1 "github.com/fluxcd/source-controller/api/v1" +) + +var exportSourceChartCmd = &cobra.Command{ + Use: "chart [name]", + Short: "Export HelmChart sources in YAML format", + Long: withPreviewNote("The export source chart command exports one or all HelmChart sources in YAML format."), + Example: ` # Export all chart sources + flux export source chart --all > sources.yaml`, + ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)), + RunE: exportCommand{ + list: helmChartListAdapter{&sourcev1.HelmChartList{}}, + object: helmChartAdapter{&sourcev1.HelmChart{}}, + }.run, +} + +func init() { + exportSourceCmd.AddCommand(exportSourceChartCmd) +} + +func exportHelmChart(source *sourcev1.HelmChart) interface{} { + gvk := sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind) + export := sourcev1.HelmChart{ + TypeMeta: metav1.TypeMeta{ + Kind: gvk.Kind, + APIVersion: gvk.GroupVersion().String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: source.Name, + Namespace: source.Namespace, + Labels: source.Labels, + Annotations: source.Annotations, + }, + Spec: source.Spec, + } + return export +} + +func (ex helmChartAdapter) export() interface{} { + return exportHelmChart(ex.HelmChart) +} + +func (ex helmChartListAdapter) exportItem(i int) interface{} { + return exportHelmChart(&ex.HelmChartList.Items[i]) +} diff --git a/cmd/flux/export_source_helm.go b/cmd/flux/export_source_helm.go index f52e5010ce..2c8da3a1d4 100644 --- a/cmd/flux/export_source_helm.go +++ b/cmd/flux/export_source_helm.go @@ -21,13 +21,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" ) var exportSourceHelmCmd = &cobra.Command{ Use: "helm [name]", Short: "Export HelmRepository sources in YAML format", - Long: withPreviewNote("The export source git command exports one or all HelmRepository sources in YAML format."), + Long: "The export source git command exports one or all HelmRepository sources in YAML format.", Example: ` # Export all HelmRepository sources flux export source helm --all > sources.yaml diff --git a/cmd/flux/export_test.go b/cmd/flux/export_test.go index 85ae174aae..5488305b18 100644 --- a/cmd/flux/export_test.go +++ b/cmd/flux/export_test.go @@ -58,6 +58,12 @@ func TestExport(t *testing.T) { "testdata/export/git-repo.yaml", tmpl, }, + { + "source chart", + "export source chart flux-system", + "testdata/export/helm-chart.yaml", + tmpl, + }, { "source helm", "export source helm flux-system", diff --git a/cmd/flux/get.go b/cmd/flux/get.go index fe70006ad9..17766f382f 100644 --- a/cmd/flux/get.go +++ b/cmd/flux/get.go @@ -18,7 +18,6 @@ package main import ( "context" - "errors" "fmt" "os" "strings" @@ -28,7 +27,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/discovery" watchtools "k8s.io/client-go/tools/watch" "sigs.k8s.io/controller-runtime/pkg/client" @@ -178,8 +176,7 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error { err = kubeClient.List(ctx, get.list.asClientList(), listOpts...) if err != nil { - var discErr *discovery.ErrGroupDiscoveryFailed - if getAll && (strings.Contains(err.Error(), "no matches for kind") || errors.As(err, &discErr)) { + if getAll && apimeta.IsNoMatchError(err) { return nil } return err diff --git a/cmd/flux/get_alert.go b/cmd/flux/get_alert.go index 9137e90e7a..54b2afe6be 100644 --- a/cmd/flux/get_alert.go +++ b/cmd/flux/get_alert.go @@ -19,12 +19,14 @@ package main import ( "fmt" "strconv" - "strings" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" ) var getAlertCmd = &cobra.Command{ @@ -76,8 +78,9 @@ func init() { func (s alertListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string { item := s.Items[i] - status, msg := statusAndMessage(item.Status.Conditions) - return append(nameColumns(&item, includeNamespace, includeKind), strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) + status, msg := string(metav1.ConditionTrue), "Alert is Ready" + return append(nameColumns(&item, includeNamespace, includeKind), + cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (s alertListAdapter) headers(includeNamespace bool) []string { @@ -89,6 +92,5 @@ func (s alertListAdapter) headers(includeNamespace bool) []string { } func (s alertListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool { - item := s.Items[i] - return statusMatches(conditionType, conditionStatus, item.Status.Conditions) + return false } diff --git a/cmd/flux/get_alertprovider.go b/cmd/flux/get_alertprovider.go index 983cbf3362..11d50cb30a 100644 --- a/cmd/flux/get_alertprovider.go +++ b/cmd/flux/get_alertprovider.go @@ -20,9 +20,10 @@ import ( "fmt" "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" ) var getAlertProviderCmd = &cobra.Command{ @@ -74,7 +75,7 @@ func init() { func (s alertProviderListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string { item := s.Items[i] - status, msg := statusAndMessage(item.Status.Conditions) + status, msg := string(metav1.ConditionTrue), "Provider is Ready" return append(nameColumns(&item, includeNamespace, includeKind), status, msg) } @@ -87,6 +88,5 @@ func (s alertProviderListAdapter) headers(includeNamespace bool) []string { } func (s alertProviderListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool { - item := s.Items[i] - return statusMatches(conditionType, conditionStatus, item.Status.Conditions) + return false } diff --git a/cmd/flux/get_all.go b/cmd/flux/get_all.go index f868b83344..69122ffdf3 100644 --- a/cmd/flux/get_all.go +++ b/cmd/flux/get_all.go @@ -17,14 +17,13 @@ limitations under the License. package main import ( - "strings" - "github.com/spf13/cobra" + apimeta "k8s.io/apimachinery/pkg/api/meta" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" notificationv1 "github.com/fluxcd/notification-controller/api/v1" - notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3" ) var getAllCmd = &cobra.Command{ @@ -63,11 +62,11 @@ var getAllCmd = &cobra.Command{ }, { apiType: alertProviderType, - list: alertProviderListAdapter{¬ificationv1b2.ProviderList{}}, + list: alertProviderListAdapter{¬ificationv1b3.ProviderList{}}, }, { apiType: alertType, - list: &alertListAdapter{¬ificationv1b2.AlertList{}}, + list: &alertListAdapter{¬ificationv1b3.AlertList{}}, }, } @@ -87,7 +86,7 @@ var getAllCmd = &cobra.Command{ } func logError(err error) { - if !strings.Contains(err.Error(), "no matches for kind") { + if !apimeta.IsNoMatchError(err) { logger.Failuref(err.Error()) } } diff --git a/cmd/flux/get_helmrelease.go b/cmd/flux/get_helmrelease.go index fd709d2953..c5a4b67e5f 100644 --- a/cmd/flux/get_helmrelease.go +++ b/cmd/flux/get_helmrelease.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Flux authors +Copyright 2024 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,18 +19,20 @@ package main import ( "fmt" "strconv" - "strings" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" "k8s.io/apimachinery/pkg/runtime" + + helmv2 "github.com/fluxcd/helm-controller/api/v2" ) var getHelmReleaseCmd = &cobra.Command{ Use: "helmreleases", Aliases: []string{"hr", "helmrelease"}, Short: "Get HelmRelease statuses", - Long: withPreviewNote("The get helmreleases command prints the statuses of the resources."), + Long: "The get helmreleases command prints the statuses of the resources.", Example: ` # List all Helm releases and their status flux get helmreleases`, ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)), @@ -70,12 +72,19 @@ func init() { getCmd.AddCommand(getHelmReleaseCmd) } +func getHelmReleaseRevision(helmRelease helmv2.HelmRelease) string { + if helmRelease.Status.History != nil && len(helmRelease.Status.History) > 0 { + return helmRelease.Status.History[0].ChartVersion + } + return helmRelease.Status.LastAttemptedRevision +} + func (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string { item := a.Items[i] - revision := item.Status.LastAppliedRevision + revision := getHelmReleaseRevision(item) status, msg := statusAndMessage(item.Status.Conditions) return append(nameColumns(&item, includeNamespace, includeKind), - revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) + revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (a helmReleaseListAdapter) headers(includeNamespace bool) []string { diff --git a/cmd/flux/get_image_all.go b/cmd/flux/get_image_all.go index 83c1680c0b..bf0fbd529d 100644 --- a/cmd/flux/get_image_all.go +++ b/cmd/flux/get_image_all.go @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" ) diff --git a/cmd/flux/get_image_repository.go b/cmd/flux/get_image_repository.go index 0f6f95a6dd..e160de8dfe 100644 --- a/cmd/flux/get_image_repository.go +++ b/cmd/flux/get_image_repository.go @@ -19,10 +19,11 @@ package main import ( "fmt" "strconv" - "strings" "time" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" "k8s.io/apimachinery/pkg/runtime" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" @@ -82,7 +83,7 @@ func (s imageRepositoryListAdapter) summariseItem(i int, includeNamespace bool, lastScan = item.Status.LastScanResult.ScanTime.Time.Format(time.RFC3339) } return append(nameColumns(&item, includeNamespace, includeKind), - lastScan, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) + lastScan, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (s imageRepositoryListAdapter) headers(includeNamespace bool) []string { diff --git a/cmd/flux/get_image_update.go b/cmd/flux/get_image_update.go index 833be4abf1..f653a055a8 100644 --- a/cmd/flux/get_image_update.go +++ b/cmd/flux/get_image_update.go @@ -19,13 +19,14 @@ package main import ( "fmt" "strconv" - "strings" "time" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" "k8s.io/apimachinery/pkg/runtime" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" ) var getImageUpdateCmd = &cobra.Command{ @@ -81,7 +82,8 @@ func (s imageUpdateAutomationListAdapter) summariseItem(i int, includeNamespace if item.Status.LastAutomationRunTime != nil { lastRun = item.Status.LastAutomationRunTime.Time.Format(time.RFC3339) } - return append(nameColumns(&item, includeNamespace, includeKind), lastRun, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) + return append(nameColumns(&item, includeNamespace, includeKind), lastRun, + cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (s imageUpdateAutomationListAdapter) headers(includeNamespace bool) []string { diff --git a/cmd/flux/get_kustomization.go b/cmd/flux/get_kustomization.go index 517aa375c4..a95ec655de 100644 --- a/cmd/flux/get_kustomization.go +++ b/cmd/flux/get_kustomization.go @@ -19,9 +19,10 @@ package main import ( "fmt" "strconv" - "strings" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" "k8s.io/apimachinery/pkg/runtime" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" @@ -83,7 +84,7 @@ func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool, in revision = utils.TruncateHex(revision) msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), - revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) + revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (a kustomizationListAdapter) headers(includeNamespace bool) []string { diff --git a/cmd/flux/get_receiver.go b/cmd/flux/get_receiver.go index a72d07659a..43f8f5a445 100644 --- a/cmd/flux/get_receiver.go +++ b/cmd/flux/get_receiver.go @@ -19,9 +19,10 @@ package main import ( "fmt" "strconv" - "strings" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" "k8s.io/apimachinery/pkg/runtime" notificationv1 "github.com/fluxcd/notification-controller/api/v1" @@ -74,7 +75,8 @@ func init() { func (s receiverListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string { item := s.Items[i] status, msg := statusAndMessage(item.Status.Conditions) - return append(nameColumns(&item, includeNamespace, includeKind), strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) + return append(nameColumns(&item, includeNamespace, includeKind), + cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (s receiverListAdapter) headers(includeNamespace bool) []string { diff --git a/cmd/flux/get_source_all.go b/cmd/flux/get_source_all.go index a7c52e85f7..5dfb9f97c2 100644 --- a/cmd/flux/get_source_all.go +++ b/cmd/flux/get_source_all.go @@ -17,9 +17,8 @@ limitations under the License. package main import ( - "strings" - "github.com/spf13/cobra" + apimeta "k8s.io/apimachinery/pkg/api/meta" sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" @@ -55,17 +54,17 @@ var getSourceAllCmd = &cobra.Command{ }, { apiType: helmRepositoryType, - list: &helmRepositoryListAdapter{&sourcev1b2.HelmRepositoryList{}}, + list: &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}}, }, { apiType: helmChartType, - list: &helmChartListAdapter{&sourcev1b2.HelmChartList{}}, + list: &helmChartListAdapter{&sourcev1.HelmChartList{}}, }, } for _, c := range allSourceCmd { if err := c.run(cmd, args); err != nil { - if !strings.Contains(err.Error(), "no matches for kind") { + if !apimeta.IsNoMatchError(err) { logger.Failuref(err.Error()) } } diff --git a/cmd/flux/get_source_bucket.go b/cmd/flux/get_source_bucket.go index b21ee2233d..b7355fdb55 100644 --- a/cmd/flux/get_source_bucket.go +++ b/cmd/flux/get_source_bucket.go @@ -19,9 +19,10 @@ package main import ( "fmt" "strconv" - "strings" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" "k8s.io/apimachinery/pkg/runtime" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" @@ -85,7 +86,7 @@ func (a *bucketListAdapter) summariseItem(i int, includeNamespace bool, includeK revision = utils.TruncateHex(revision) msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), - revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) + revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (a bucketListAdapter) headers(includeNamespace bool) []string { diff --git a/cmd/flux/get_source_chart.go b/cmd/flux/get_source_chart.go index e83839cb12..4f0784534a 100644 --- a/cmd/flux/get_source_chart.go +++ b/cmd/flux/get_source_chart.go @@ -19,12 +19,13 @@ package main import ( "fmt" "strconv" - "strings" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" "k8s.io/apimachinery/pkg/runtime" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" "github.com/fluxcd/flux2/v2/internal/utils" ) @@ -32,7 +33,7 @@ import ( var getSourceHelmChartCmd = &cobra.Command{ Use: "chart", Short: "Get HelmChart statuses", - Long: withPreviewNote("The get sources chart command prints the status of the HelmCharts."), + Long: "The get sources chart command prints the status of the HelmCharts.", Example: ` # List all Helm charts and their status flux get sources chart @@ -86,7 +87,7 @@ func (a *helmChartListAdapter) summariseItem(i int, includeNamespace bool, inclu // Message may still contain reference of e.g. commit chart was build from msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), - revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) + revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (a helmChartListAdapter) headers(includeNamespace bool) []string { diff --git a/cmd/flux/get_source_git.go b/cmd/flux/get_source_git.go index 3c71da1c50..78476de55c 100644 --- a/cmd/flux/get_source_git.go +++ b/cmd/flux/get_source_git.go @@ -19,9 +19,10 @@ package main import ( "fmt" "strconv" - "strings" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" "k8s.io/apimachinery/pkg/runtime" sourcev1 "github.com/fluxcd/source-controller/api/v1" @@ -85,7 +86,7 @@ func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i revision = utils.TruncateHex(revision) msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), - revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) + revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (a gitRepositoryListAdapter) headers(includeNamespace bool) []string { diff --git a/cmd/flux/get_source_helm.go b/cmd/flux/get_source_helm.go index 28315eb79c..eef666d4b8 100644 --- a/cmd/flux/get_source_helm.go +++ b/cmd/flux/get_source_helm.go @@ -19,12 +19,14 @@ package main import ( "fmt" "strconv" - "strings" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" "github.com/fluxcd/flux2/v2/internal/utils" ) @@ -32,7 +34,7 @@ import ( var getSourceHelmCmd = &cobra.Command{ Use: "helm", Short: "Get HelmRepository source statuses", - Long: withPreviewNote("The get sources helm command prints the status of the HelmRepository sources."), + Long: "The get sources helm command prints the status of the HelmRepository sources.", Example: ` # List all Helm repositories and their status flux get sources helm @@ -81,11 +83,16 @@ func (a *helmRepositoryListAdapter) summariseItem(i int, includeNamespace bool, if item.GetArtifact() != nil { revision = item.GetArtifact().Revision } - status, msg := statusAndMessage(item.Status.Conditions) + var status, msg string + if item.Spec.Type == sourcev1.HelmRepositoryTypeOCI { + status, msg = string(metav1.ConditionTrue), "Helm repository is Ready" + } else { + status, msg = statusAndMessage(item.Status.Conditions) + } revision = utils.TruncateHex(revision) msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), - revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) + revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (a helmRepositoryListAdapter) headers(includeNamespace bool) []string { diff --git a/cmd/flux/get_source_oci.go b/cmd/flux/get_source_oci.go index 36f93fc90c..e5bbe40956 100644 --- a/cmd/flux/get_source_oci.go +++ b/cmd/flux/get_source_oci.go @@ -19,9 +19,10 @@ package main import ( "fmt" "strconv" - "strings" "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" "k8s.io/apimachinery/pkg/runtime" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" @@ -85,7 +86,7 @@ func (a *ociRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i revision = utils.TruncateHex(revision) msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), - revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) + revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg) } func (a ociRepositoryListAdapter) headers(includeNamespace bool) []string { diff --git a/cmd/flux/helmrelease.go b/cmd/flux/helmrelease.go index 81deb149d0..08cad0272e 100644 --- a/cmd/flux/helmrelease.go +++ b/cmd/flux/helmrelease.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Flux authors +Copyright 2024 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package main import ( "sigs.k8s.io/controller-runtime/pkg/client" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" ) // helmv2.HelmRelease diff --git a/cmd/flux/image.go b/cmd/flux/image.go index 38c264a7c3..621dabfc4f 100644 --- a/cmd/flux/image.go +++ b/cmd/flux/image.go @@ -19,7 +19,7 @@ package main import ( "sigs.k8s.io/controller-runtime/pkg/client" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" ) diff --git a/cmd/flux/install.go b/cmd/flux/install.go index 537120218f..390f8616c4 100644 --- a/cmd/flux/install.go +++ b/cmd/flux/install.go @@ -21,19 +21,26 @@ import ( "fmt" "os" "path/filepath" + "strings" "time" + "github.com/manifoldco/promptui" "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/yaml" "github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/pkg/manifestgen" "github.com/fluxcd/flux2/v2/pkg/manifestgen/install" + "github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/v2/pkg/status" ) var installCmd = &cobra.Command{ Use: "install", + Args: cobra.NoArgs, Short: "Install or upgrade Flux", Long: `The install command deploys Flux in the specified namespace. If a previous version is installed, then an in-place upgrade will be performed.`, @@ -63,6 +70,7 @@ type installFlags struct { defaultComponents []string extraComponents []string registry string + registryCredential string imagePullSecret string branch string watchAllNamespaces bool @@ -72,6 +80,7 @@ type installFlags struct { tokenAuth bool clusterDomain string tolerationKeys []string + force bool } var installArgs = NewInstallFlags() @@ -88,6 +97,8 @@ func init() { installCmd.Flags().StringVar(&installArgs.manifestsPath, "manifests", "", "path to the manifest directory") installCmd.Flags().StringVar(&installArgs.registry, "registry", rootArgs.defaults.Registry, "container registry where the toolkit images are published") + installCmd.Flags().StringVar(&installArgs.registryCredential, "registry-creds", "", + "container registry credentials in the format 'user:password', requires --image-pull-secret to be set") installCmd.Flags().StringVar(&installArgs.imagePullSecret, "image-pull-secret", "", "Kubernetes secret name used for pulling the toolkit images from a private registry") installCmd.Flags().BoolVar(&installArgs.watchAllNamespaces, "watch-all-namespaces", rootArgs.defaults.WatchAllNamespaces, @@ -98,6 +109,7 @@ func init() { installCmd.Flags().StringVar(&installArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain") installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil, "list of toleration keys used to schedule the components pods onto nodes with matching taints") + installCmd.Flags().BoolVar(&installArgs.force, "force", false, "override existing Flux installation if it's managed by a different tool such as Helm") installCmd.Flags().MarkHidden("manifests") rootCmd.AddCommand(installCmd) @@ -119,6 +131,14 @@ func installCmdRun(cmd *cobra.Command, args []string) error { return err } + if installArgs.registryCredential != "" && installArgs.imagePullSecret == "" { + return fmt.Errorf("--registry-creds requires --image-pull-secret to be set") + } + + if installArgs.registryCredential != "" && len(strings.Split(installArgs.registryCredential, ":")) != 2 { + return fmt.Errorf("invalid --registry-creds format, expected 'user:password'") + } + if ver, err := getVersion(installArgs.version); err != nil { return err } else { @@ -149,6 +169,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error { Namespace: *kubeconfigArgs.Namespace, Components: components, Registry: installArgs.registry, + RegistryCredential: installArgs.registryCredential, ImagePullSecret: installArgs.imagePullSecret, WatchAllNamespaces: installArgs.watchAllNamespaces, NetworkPolicy: installArgs.networkPolicy, @@ -183,6 +204,35 @@ func installCmdRun(cmd *cobra.Command, args []string) error { logger.Successf("manifests build completed") logger.Actionf("installing components in %s namespace", *kubeconfigArgs.Namespace) + kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) + if err != nil { + return err + } + + installed := true + info, err := getFluxClusterInfo(ctx, kubeClient) + if err != nil { + if !errors.IsNotFound(err) { + return fmt.Errorf("cluster info unavailable: %w", err) + } + installed = false + } + + if info.bootstrapped { + return fmt.Errorf("this cluster has already been bootstrapped with Flux %s! Please use 'flux bootstrap' to upgrade", + info.version) + } + + if installed && !installArgs.force { + err := confirmFluxInstallOverride(info) + if err != nil { + if err == promptui.ErrAbort { + return fmt.Errorf("installation cancelled") + } + return err + } + } + applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path)) if err != nil { return fmt.Errorf("install failed: %w", err) @@ -190,6 +240,29 @@ func installCmdRun(cmd *cobra.Command, args []string) error { fmt.Fprintln(os.Stderr, applyOutput) + if opts.ImagePullSecret != "" && opts.RegistryCredential != "" { + logger.Actionf("generating image pull secret %s", opts.ImagePullSecret) + credentials := strings.SplitN(opts.RegistryCredential, ":", 2) + secretOpts := sourcesecret.Options{ + Name: opts.ImagePullSecret, + Namespace: opts.Namespace, + Registry: opts.Registry, + Username: credentials[0], + Password: credentials[1], + } + imagePullSecret, err := sourcesecret.Generate(secretOpts) + if err != nil { + return fmt.Errorf("install failed: %w", err) + } + var s corev1.Secret + if err := yaml.Unmarshal([]byte(imagePullSecret.Content), &s); err != nil { + return fmt.Errorf("install failed: %w", err) + } + if err := upsertSecret(ctx, kubeClient, s); err != nil { + return fmt.Errorf("install failed: %w", err) + } + } + kubeConfig, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions) if err != nil { return fmt.Errorf("install failed: %w", err) diff --git a/cmd/flux/install_test.go b/cmd/flux/install_test.go index f10f7d62a2..161a3d36fb 100644 --- a/cmd/flux/install_test.go +++ b/cmd/flux/install_test.go @@ -37,6 +37,16 @@ func TestInstall(t *testing.T) { args: "install --namespace='@#[]'", assert: assertError("namespace must be a valid DNS label: \"@#[]\""), }, + { + name: "invalid sub-command", + args: "install unexpectedPosArg --namespace=example", + assert: assertError(`unknown command "unexpectedPosArg" for "flux install"`), + }, + { + name: "missing image pull secret", + args: "install --registry-creds=fluxcd:test", + assert: assertError(`--registry-creds requires --image-pull-secret to be set`), + }, } for _, tt := range tests { diff --git a/cmd/flux/logs.go b/cmd/flux/logs.go index 84d68acb23..0931b1c919 100644 --- a/cmd/flux/logs.go +++ b/cmd/flux/logs.go @@ -74,7 +74,7 @@ type logsFlags struct { fluxNamespace string allNamespaces bool sinceTime string - sinceSeconds time.Duration + sinceDuration time.Duration } var logsArgs = logsFlags{ @@ -91,7 +91,7 @@ func init() { logsCmd.Flags().Int64VarP(&logsArgs.tail, "tail", "", logsArgs.tail, "lines of recent log file to display") logsCmd.Flags().StringVarP(&logsArgs.fluxNamespace, "flux-namespace", "", rootArgs.defaults.Namespace, "the namespace where the Flux components are running") logsCmd.Flags().BoolVarP(&logsArgs.allNamespaces, "all-namespaces", "A", false, "displays logs for objects across all namespaces") - logsCmd.Flags().DurationVar(&logsArgs.sinceSeconds, "since", logsArgs.sinceSeconds, "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.") + logsCmd.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.") logsCmd.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.") rootCmd.AddCommand(logsCmd) } @@ -129,8 +129,8 @@ func logsCmdRun(cmd *cobra.Command, args []string) error { logOpts.TailLines = &logsArgs.tail } - if len(logsArgs.sinceTime) > 0 && logsArgs.sinceSeconds != 0 { - return fmt.Errorf("at most one of `sinceTime` or `sinceSeconds` may be specified") + if len(logsArgs.sinceTime) > 0 && logsArgs.sinceDuration != 0 { + return fmt.Errorf("at most one of `sinceTime` or `sinceDuration` may be specified") } if len(logsArgs.sinceTime) > 0 { @@ -141,9 +141,9 @@ func logsCmdRun(cmd *cobra.Command, args []string) error { logOpts.SinceTime = &t } - if logsArgs.sinceSeconds != 0 { + if logsArgs.sinceDuration != 0 { // round up to the nearest second - sec := int64(logsArgs.sinceSeconds.Round(time.Second).Seconds()) + sec := int64(logsArgs.sinceDuration.Round(time.Second).Seconds()) logOpts.SinceSeconds = &sec } diff --git a/cmd/flux/logs_e2e_test.go b/cmd/flux/logs_e2e_test.go index 65a2d76e60..46c1c11e71 100644 --- a/cmd/flux/logs_e2e_test.go +++ b/cmd/flux/logs_e2e_test.go @@ -82,7 +82,7 @@ func TestLogsSinceTimeInvalid(t *testing.T) { func TestLogsSinceOnlyOneAllowed(t *testing.T) { cmd := cmdTestCase{ args: "logs --since=2m --since-time=2021-08-06T14:26:25.546Z", - assert: assertError("at most one of `sinceTime` or `sinceSeconds` may be specified"), + assert: assertError("at most one of `sinceTime` or `sinceDuration` may be specified"), } cmd.runTestCmd(t) } diff --git a/cmd/flux/main.go b/cmd/flux/main.go index 9485110cb1..1226a3474d 100644 --- a/cmd/flux/main.go +++ b/cmd/flux/main.go @@ -151,6 +151,11 @@ func init() { apiServer := "" kubeconfigArgs.APIServer = &apiServer rootCmd.PersistentFlags().StringVar(kubeconfigArgs.APIServer, "server", *kubeconfigArgs.APIServer, "The address and port of the Kubernetes API server") + // Update the description for kubeconfig TLS flags so that user's don't mistake it for a Flux specific flag + rootCmd.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" + rootCmd.Flag("client-certificate").Usage = "Path to a client certificate file for TLS authentication to the Kubernetes API server" + rootCmd.Flag("certificate-authority").Usage = "Path to a cert file for the certificate authority to authenticate the Kubernetes API server" + rootCmd.Flag("client-key").Usage = "Path to a client key file for TLS authentication to the Kubernetes API server" kubeclientOptions.BindFlags(rootCmd.PersistentFlags()) diff --git a/cmd/flux/main_e2e_test.go b/cmd/flux/main_e2e_test.go index 1cfa3acef2..f2b01cc652 100644 --- a/cmd/flux/main_e2e_test.go +++ b/cmd/flux/main_e2e_test.go @@ -25,10 +25,15 @@ import ( "os" "testing" + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/fluxcd/flux2/v2/internal/utils" ) func TestMain(m *testing.M) { + log.SetLogger(logr.New(log.NullLogSink{})) + // Ensure tests print consistent timestamps regardless of timezone os.Setenv("TZ", "UTC") @@ -41,7 +46,7 @@ func TestMain(m *testing.M) { // Install Flux. output, err := executeCommand("install --components-extra=image-reflector-controller,image-automation-controller") if err != nil { - panic(fmt.Errorf("install falied: %s error:'%w'", output, err)) + panic(fmt.Errorf("install failed: %s error:'%w'", output, err)) } // Run tests @@ -50,7 +55,7 @@ func TestMain(m *testing.M) { // Uninstall Flux output, err = executeCommand("uninstall -s --keep-namespace") if err != nil { - panic(fmt.Errorf("uninstall falied: %s error:'%w'", output, err)) + panic(fmt.Errorf("uninstall failed: %s error:'%w'", output, err)) } // Delete namespace and wait for finalisation diff --git a/cmd/flux/main_test.go b/cmd/flux/main_test.go index 37398309f0..5f2afe6083 100644 --- a/cmd/flux/main_test.go +++ b/cmd/flux/main_test.go @@ -31,14 +31,16 @@ import ( "text/template" "time" - "github.com/fluxcd/flux2/v2/internal/utils" "github.com/google/go-cmp/cmp" "github.com/mattn/go-shellwords" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" k8syaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" + + "github.com/fluxcd/flux2/v2/internal/utils" ) var nextNamespaceId int64 @@ -112,7 +114,8 @@ func (m *testEnvKubeManager) CreateObjects(clientObjects []*unstructured.Unstruc } obj.SetResourceVersion(createObj.GetResourceVersion()) err = m.client.Status().Update(context.Background(), obj) - if err != nil { + // Updating status of static objects results in not found error. + if err != nil && !errors.IsNotFound(err) { return err } } @@ -182,7 +185,7 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager } tmpFilename := filepath.Join("/tmp", "kubeconfig-"+time.Nanosecond.String()) - os.WriteFile(tmpFilename, kubeConfig, 0644) + os.WriteFile(tmpFilename, kubeConfig, 0o600) k8sClient, err := client.NewWithWatch(cfg, client.Options{ Scheme: utils.NewScheme(), }) @@ -203,6 +206,9 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager useExistingCluster := true config, err := clientcmd.BuildConfigFromFlags("", testKubeConfig) + if err != nil { + return nil, err + } testEnv := &envtest.Environment{ UseExistingCluster: &useExistingCluster, Config: config, @@ -310,7 +316,7 @@ func assertGoldenTemplateFile(goldenFile string, templateValues map[string]strin if len(templateValues) > 0 { fmt.Println("NOTE: -update flag passed but golden template files can't be updated, please update it manually") } else { - if err := os.WriteFile(goldenFile, []byte(output), 0644); err != nil { + if err := os.WriteFile(goldenFile, []byte(output), 0o600); err != nil { return fmt.Errorf("failed to update golden file '%s': %v", goldenFile, err) } return nil @@ -337,8 +343,6 @@ type cmdTestCase struct { // Tests use assertFunc to assert on an output, success or failure. This // can be a function defined by the test or existing function above. assert assertFunc - // Filename that contains yaml objects to load into Kubernetes - objectFile string } func (cmd *cmdTestCase) runTestCmd(t *testing.T) { @@ -390,6 +394,29 @@ func executeCommand(cmd string) (string, error) { return result, err } +// Run the command while passing the string as input and return the captured output. +func executeCommandWithIn(cmd string, in io.Reader) (string, error) { + defer resetCmdArgs() + args, err := shellwords.Parse(cmd) + if err != nil { + return "", err + } + + buf := new(bytes.Buffer) + + rootCmd.SetOut(buf) + rootCmd.SetErr(buf) + rootCmd.SetArgs(args) + if in != nil { + rootCmd.SetIn(in) + } + + _, err = rootCmd.ExecuteC() + result := buf.String() + + return result, err +} + // resetCmdArgs resets the flags for various cmd // Note: this will also clear default value of the flags set in init() func resetCmdArgs() { @@ -438,7 +465,7 @@ func resetCmdArgs() { versionArgs = versionFlags{ output: "yaml", } - + envsubstArgs = envsubstFlags{} } func isChangeError(err error) bool { diff --git a/cmd/flux/main_unit_test.go b/cmd/flux/main_unit_test.go index 8d3e639862..8e0af1aa59 100644 --- a/cmd/flux/main_unit_test.go +++ b/cmd/flux/main_unit_test.go @@ -22,10 +22,13 @@ package main import ( "context" "fmt" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" "testing" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/log" ) // The test environment is long running process shared between tests, initialized @@ -34,6 +37,8 @@ import ( var testEnv *testEnvKubeManager func TestMain(m *testing.M) { + log.SetLogger(logr.New(log.NullLogSink{})) + // Ensure tests print consistent timestamps regardless of timezone os.Setenv("TZ", "UTC") diff --git a/cmd/flux/pull_artifact.go b/cmd/flux/pull_artifact.go index d61f6dcbc5..114368bd38 100644 --- a/cmd/flux/pull_artifact.go +++ b/cmd/flux/pull_artifact.go @@ -111,8 +111,12 @@ func pullArtifactCmdRun(cmd *cobra.Command, args []string) error { return err } - logger.Successf("source %s", meta.Source) - logger.Successf("revision %s", meta.Revision) + if meta.Source != "" { + logger.Successf("source %s", meta.Source) + } + if meta.Revision != "" { + logger.Successf("revision %s", meta.Revision) + } logger.Successf("digest %s", meta.Digest) logger.Successf("artifact content extracted to %s", pullArtifactArgs.output) diff --git a/cmd/flux/push_artifact.go b/cmd/flux/push_artifact.go index 3ebd3fbe8a..735b78d51a 100644 --- a/cmd/flux/push_artifact.go +++ b/cmd/flux/push_artifact.go @@ -28,7 +28,6 @@ import ( "github.com/google/go-containerregistry/pkg/crane" "github.com/google/go-containerregistry/pkg/logs" "github.com/google/go-containerregistry/pkg/name" - reg "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/spf13/cobra" @@ -106,15 +105,16 @@ The command can read the credentials from '~/.docker/config.json' but they can a } type pushArtifactFlags struct { - path string - source string - revision string - creds string - provider flags.SourceOCIProvider - ignorePaths []string - annotations []string - output string - debug bool + path string + source string + revision string + creds string + provider flags.SourceOCIProvider + ignorePaths []string + annotations []string + output string + debug bool + reproducible bool } var pushArtifactArgs = newPushArtifactFlags() @@ -136,6 +136,7 @@ func init() { pushArtifactCmd.Flags().StringVarP(&pushArtifactArgs.output, "output", "o", "", "the format in which the artifact digest should be printed, can be 'json' or 'yaml'") pushArtifactCmd.Flags().BoolVarP(&pushArtifactArgs.debug, "debug", "", false, "display logs from underlying library") + pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.reproducible, "reproducible", false, "ensure reproducible image digests by setting the created timestamp to '1970-01-01T00:00:00Z'") pushCmd.AddCommand(pushArtifactCmd) } @@ -203,6 +204,11 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error { Annotations: annotations, } + if pushArtifactArgs.reproducible { + zeroTime := time.Unix(0, 0) + meta.Created = zeroTime.Format(time.RFC3339) + } + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) defer cancel() @@ -261,17 +267,20 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error { } ociClient := client.NewClient(opts) - digestURL, err := ociClient.Push(ctx, url, path, meta, pushArtifactArgs.ignorePaths) + digestURL, err := ociClient.Push(ctx, url, path, + client.WithPushMetadata(meta), + client.WithPushIgnorePaths(pushArtifactArgs.ignorePaths...), + ) if err != nil { return fmt.Errorf("pushing artifact failed: %w", err) } - digest, err := reg.NewDigest(digestURL) + digest, err := name.NewDigest(digestURL) if err != nil { return fmt.Errorf("artifact digest parsing failed: %w", err) } - tag, err := reg.NewTag(url) + tag, err := name.NewTag(url) if err != nil { return fmt.Errorf("artifact tag parsing failed: %w", err) } diff --git a/cmd/flux/readiness.go b/cmd/flux/readiness.go new file mode 100644 index 0000000000..f201625f6f --- /dev/null +++ b/cmd/flux/readiness.go @@ -0,0 +1,149 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + + kstatus "github.com/fluxcd/cli-utils/pkg/kstatus/status" + apimeta "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/object" + "github.com/fluxcd/pkg/runtime/patch" +) + +// objectStatusType is the type of object in terms of status when computing the +// readiness of an object. Readiness check method depends on the type of object. +// For a dynamic object, Ready status condition is considered only for the +// latest generation of the object. For a static object that don't have any +// condition, the object generation is not considered. +type objectStatusType int + +const ( + objectStatusDynamic objectStatusType = iota + objectStatusStatic +) + +// isObjectReady determines if an object is ready using the kstatus.Compute() +// result. statusType helps differenciate between static and dynamic objects to +// accurately check the object's readiness. A dynamic object may have some extra +// considerations depending on the object. +func isObjectReady(obj client.Object, statusType objectStatusType) (bool, error) { + observedGen, err := object.GetStatusObservedGeneration(obj) + if err != nil && err != object.ErrObservedGenerationNotFound { + return false, err + } + + if statusType == objectStatusDynamic { + // Object not reconciled yet. + if observedGen < 1 { + return false, nil + } + + cobj, ok := obj.(meta.ObjectWithConditions) + if !ok { + return false, fmt.Errorf("unable to get conditions from object") + } + + if c := apimeta.FindStatusCondition(cobj.GetConditions(), meta.ReadyCondition); c != nil { + // Ensure that the ready condition is for the latest generation of + // the object. + // NOTE: Some APIs like ImageUpdateAutomation and HelmRelease don't + // support per condition observed generation yet. Per condition + // observed generation for them are always zero. + // There are two strategies used across different object kinds to + // check the latest ready condition: + // - check that the ready condition's generation matches the + // object's generation. + // - check that the observed generation of the object in the + // status matches the object's generation. + // + // TODO: Once ImageUpdateAutomation and HelmRelease APIs have per + // condition observed generation, remove the object's observed + // generation and object's generation check (the second condition + // below). Also, try replacing this readiness check function with + // fluxcd/pkg/ssa's ResourceManager.Wait(), which uses kstatus + // internally to check readiness of the objects. + if c.ObservedGeneration != 0 && c.ObservedGeneration != obj.GetGeneration() { + return false, nil + } + if c.ObservedGeneration == 0 && observedGen != obj.GetGeneration() { + return false, nil + } + } else { + return false, nil + } + } + + u, err := patch.ToUnstructured(obj) + if err != nil { + return false, err + } + result, err := kstatus.Compute(u) + if err != nil { + return false, err + } + switch result.Status { + case kstatus.CurrentStatus: + return true, nil + case kstatus.InProgressStatus: + return false, nil + default: + return false, fmt.Errorf(result.Message) + } +} + +// isObjectReadyConditionFunc returns a wait.ConditionFunc to be used with +// wait.Poll* while polling for an object with dynamic status to be ready. +func isObjectReadyConditionFunc(kubeClient client.Client, namespaceName types.NamespacedName, obj client.Object) wait.ConditionWithContextFunc { + return func(ctx context.Context) (bool, error) { + err := kubeClient.Get(ctx, namespaceName, obj) + if err != nil { + return false, err + } + + return isObjectReady(obj, objectStatusDynamic) + } +} + +// isStaticObjectReadyConditionFunc returns a wait.ConditionFunc to be used with +// wait.Poll* while polling for an object with static or no status to be +// ready. +func isStaticObjectReadyConditionFunc(kubeClient client.Client, namespaceName types.NamespacedName, obj client.Object) wait.ConditionWithContextFunc { + return func(ctx context.Context) (bool, error) { + err := kubeClient.Get(ctx, namespaceName, obj) + if err != nil { + return false, err + } + + return isObjectReady(obj, objectStatusStatic) + } +} + +// kstatusCompute returns the kstatus computed result of a given object. +func kstatusCompute(obj client.Object) (result *kstatus.Result, err error) { + u, err := patch.ToUnstructured(obj) + if err != nil { + return result, err + } + return kstatus.Compute(u) +} diff --git a/cmd/flux/readiness_test.go b/cmd/flux/readiness_test.go new file mode 100644 index 0000000000..4d987042f0 --- /dev/null +++ b/cmd/flux/readiness_test.go @@ -0,0 +1,139 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/conditions" + sourcev1 "github.com/fluxcd/source-controller/api/v1" +) + +func Test_isObjectReady(t *testing.T) { + // Ready object. + readyObj := &sourcev1.GitRepository{} + readyObj.Generation = 1 + readyObj.Status.ObservedGeneration = 1 + conditions.MarkTrue(readyObj, meta.ReadyCondition, "foo1", "bar1") + + // Not ready object. + notReadyObj := readyObj.DeepCopy() + conditions.MarkFalse(notReadyObj, meta.ReadyCondition, "foo2", "bar2") + + // Not reconciled object. + notReconciledObj := readyObj.DeepCopy() + notReconciledObj.Status = sourcev1.GitRepositoryStatus{ObservedGeneration: -1} + + // No condition. + noConditionObj := readyObj.DeepCopy() + noConditionObj.Status = sourcev1.GitRepositoryStatus{ObservedGeneration: 1} + + // Outdated condition. + readyObjOutdated := readyObj.DeepCopy() + readyObjOutdated.Generation = 2 + + // Object without per condition observed generation. + oldObj := readyObj.DeepCopy() + readyTrueCondn := conditions.TrueCondition(meta.ReadyCondition, "foo3", "bar3") + oldObj.Status.Conditions = []metav1.Condition{*readyTrueCondn} + + // Outdated object without per condition observed generation. + oldObjOutdated := oldObj.DeepCopy() + oldObjOutdated.Generation = 2 + + // Empty status object. + staticObj := readyObj.DeepCopy() + staticObj.Status = sourcev1.GitRepositoryStatus{} + + // No status object. + noStatusObj := ¬ificationv1.Provider{} + noStatusObj.Generation = 1 + + type args struct { + obj client.Object + statusType objectStatusType + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "dynamic ready", + args: args{obj: readyObj, statusType: objectStatusDynamic}, + want: true, + }, + { + name: "dynamic not ready", + args: args{obj: notReadyObj, statusType: objectStatusDynamic}, + want: false, + }, + { + name: "dynamic not reconciled", + args: args{obj: notReconciledObj, statusType: objectStatusDynamic}, + want: false, + }, + { + name: "dynamic not condition", + args: args{obj: noConditionObj, statusType: objectStatusDynamic}, + want: false, + }, + { + name: "dynamic ready outdated", + args: args{obj: readyObjOutdated, statusType: objectStatusDynamic}, + want: false, + }, + { + name: "dynamic ready without per condition gen", + args: args{obj: oldObj, statusType: objectStatusDynamic}, + want: true, + }, + { + name: "dynamic outdated ready status without per condition gen", + args: args{obj: oldObjOutdated, statusType: objectStatusDynamic}, + want: false, + }, + { + name: "static empty status", + args: args{obj: staticObj, statusType: objectStatusStatic}, + want: true, + }, + { + name: "static no status", + args: args{obj: noStatusObj, statusType: objectStatusStatic}, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := isObjectReady(tt.args.obj, tt.args.statusType) + if (err != nil) != tt.wantErr { + t.Errorf("isObjectReady() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("isObjectReady() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cmd/flux/reconcile.go b/cmd/flux/reconcile.go index 56f5f27167..c37f3d32ff 100644 --- a/cmd/flux/reconcile.go +++ b/cmd/flux/reconcile.go @@ -21,6 +21,7 @@ import ( "fmt" "time" + kstatus "github.com/fluxcd/cli-utils/pkg/kstatus/status" "github.com/spf13/cobra" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -30,8 +31,7 @@ import ( "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" - notificationv1 "github.com/fluxcd/notification-controller/api/v1" - notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2" + helmv2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/flux2/v2/internal/utils" @@ -61,6 +61,7 @@ type reconcilable interface { GetAnnotations() map[string]string SetAnnotations(map[string]string) + isStatic() bool // is it a static object that does not have a reconciler? lastHandledReconcileRequest() string // what was the last handled reconcile request? successMessage() string // what do you want to tell people when successfully reconciled? } @@ -101,6 +102,11 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error { return err } + if reconcile.object.isStatic() { + logger.Successf("reconciliation not supported by the object") + return nil + } + if reconcile.object.isSuspended() { return fmt.Errorf("resource is suspended") } @@ -112,20 +118,10 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error { } logger.Successf("%s annotated", reconcile.kind) - if reconcile.kind == notificationv1b2.AlertKind || reconcile.kind == notificationv1.ReceiverKind { - if err = wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isReconcileReady(ctx, kubeClient, namespacedName, reconcile.object)); err != nil { - return err - } - - logger.Successf(reconcile.object.successMessage()) - return nil - } - lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest() logger.Waitingf("waiting for %s reconciliation", reconcile.kind) - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil { + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, + reconciliationHandled(kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil { return err } readyCond := apimeta.FindStatusCondition(reconcilableConditions(reconcile.object), meta.ReadyCondition) @@ -140,16 +136,23 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error { return nil } -func reconciliationHandled(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionFunc { - return func() (bool, error) { +func reconciliationHandled(kubeClient client.Client, namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionWithContextFunc { + return func(ctx context.Context) (bool, error) { err := kubeClient.Get(ctx, namespacedName, obj.asClientObject()) if err != nil { return false, err } - isProgressing := apimeta.IsStatusConditionPresentAndEqual(reconcilableConditions(obj), - meta.ReadyCondition, metav1.ConditionUnknown) - return obj.lastHandledReconcileRequest() != lastHandledReconcileAt && !isProgressing, nil + + if obj.lastHandledReconcileRequest() == lastHandledReconcileAt { + return false, nil + } + + result, err := kstatusCompute(obj.asClientObject()) + if err != nil { + return false, err + } + + return result.Status == kstatus.CurrentStatus, nil } } @@ -164,34 +167,26 @@ func requestReconciliation(ctx context.Context, kubeClient client.Client, return err } patch := client.MergeFrom(object.DeepCopy()) - if ann := object.GetAnnotations(); ann == nil { - object.SetAnnotations(map[string]string{ - meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), - }) - } else { - ann[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) - object.SetAnnotations(ann) - } - return kubeClient.Patch(ctx, object, patch) - }) -} -func isReconcileReady(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, obj reconcilable) wait.ConditionFunc { - return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, obj.asClientObject()) - if err != nil { - return false, err + // Add a timestamp annotation to trigger a reconciliation. + ts := time.Now().Format(time.RFC3339Nano) + annotations := object.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string, 1) } + annotations[meta.ReconcileRequestAnnotation] = ts - if c := apimeta.FindStatusCondition(reconcilableConditions(obj), meta.ReadyCondition); c != nil { - switch c.Status { - case metav1.ConditionTrue: - return true, nil - case metav1.ConditionFalse: - return false, fmt.Errorf(c.Message) + // HelmRelease specific annotations to force or reset a release. + if gvk.Kind == helmv2.HelmReleaseKind { + if rhrArgs.syncForce { + annotations[helmv2.ForceRequestAnnotation] = ts + } + if rhrArgs.syncReset { + annotations[helmv2.ResetRequestAnnotation] = ts } } - return false, nil - } + + object.SetAnnotations(annotations) + return kubeClient.Patch(ctx, object, patch) + }) } diff --git a/cmd/flux/reconcile_alert.go b/cmd/flux/reconcile_alert.go deleted file mode 100644 index 655b366f0c..0000000000 --- a/cmd/flux/reconcile_alert.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright 2020 The Flux authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "github.com/spf13/cobra" - - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" -) - -var reconcileAlertCmd = &cobra.Command{ - Use: "alert [name]", - Short: "Reconcile an Alert", - Long: `The reconcile alert command triggers a reconciliation of an Alert resource and waits for it to finish.`, - Example: ` # Trigger a reconciliation for an existing alert - flux reconcile alert main`, - ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)), - RunE: reconcileCommand{ - apiType: alertType, - object: alertAdapter{¬ificationv1.Alert{}}, - }.run, -} - -func init() { - reconcileCmd.AddCommand(reconcileAlertCmd) -} - -func (obj alertAdapter) lastHandledReconcileRequest() string { - return "" -} diff --git a/cmd/flux/reconcile_alertprovider.go b/cmd/flux/reconcile_alertprovider.go deleted file mode 100644 index becf980f38..0000000000 --- a/cmd/flux/reconcile_alertprovider.go +++ /dev/null @@ -1,93 +0,0 @@ -/* -Copyright 2020 The Flux authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "context" - "fmt" - "time" - - "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" - "github.com/fluxcd/pkg/apis/meta" - - "github.com/fluxcd/flux2/v2/internal/utils" -) - -var reconcileAlertProviderCmd = &cobra.Command{ - Use: "alert-provider [name]", - Short: "Reconcile a Provider", - Long: `The reconcile alert-provider command triggers a reconciliation of a Provider resource and waits for it to finish.`, - Example: ` # Trigger a reconciliation for an existing provider - flux reconcile alert-provider slack`, - ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)), - RunE: reconcileAlertProviderCmdRun, -} - -func init() { - reconcileCmd.AddCommand(reconcileAlertProviderCmd) -} - -func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return fmt.Errorf("Provider name is required") - } - name := args[0] - - ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) - defer cancel() - - kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) - if err != nil { - return err - } - - namespacedName := types.NamespacedName{ - Namespace: *kubeconfigArgs.Namespace, - Name: name, - } - - logger.Actionf("annotating Provider %s in %s namespace", name, *kubeconfigArgs.Namespace) - var alertProvider notificationv1.Provider - err = kubeClient.Get(ctx, namespacedName, &alertProvider) - if err != nil { - return err - } - - if alertProvider.Annotations == nil { - alertProvider.Annotations = map[string]string{ - meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), - } - } else { - alertProvider.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) - } - if err := kubeClient.Update(ctx, &alertProvider); err != nil { - return err - } - logger.Successf("Provider annotated") - - logger.Waitingf("waiting for reconciliation") - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isAlertProviderReady(ctx, kubeClient, namespacedName, &alertProvider)); err != nil { - return err - } - logger.Successf("Provider reconciliation completed") - return nil -} diff --git a/cmd/flux/reconcile_helmrelease.go b/cmd/flux/reconcile_helmrelease.go index b015aa8b2d..134cfa472f 100644 --- a/cmd/flux/reconcile_helmrelease.go +++ b/cmd/flux/reconcile_helmrelease.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Flux authors +Copyright 2024 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,7 +22,8 @@ import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/types" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" ) @@ -46,13 +47,16 @@ The reconcile kustomization command triggers a reconciliation of a HelmRelease r type reconcileHelmReleaseFlags struct { syncHrWithSource bool + syncForce bool + syncReset bool } var rhrArgs reconcileHelmReleaseFlags func init() { reconcileHrCmd.Flags().BoolVar(&rhrArgs.syncHrWithSource, "with-source", false, "reconcile HelmRelease source") - + reconcileHrCmd.Flags().BoolVar(&rhrArgs.syncForce, "force", false, "force a one-off install or upgrade of the HelmRelease resource") + reconcileHrCmd.Flags().BoolVar(&rhrArgs.syncReset, "reset", false, "reset the failure count for this HelmRelease resource") reconcileCmd.AddCommand(reconcileHrCmd) } @@ -65,19 +69,49 @@ func (obj helmReleaseAdapter) reconcileSource() bool { } func (obj helmReleaseAdapter) getSource() (reconcileSource, types.NamespacedName) { - cmd := reconcileWithSourceCommand{ - apiType: helmChartType, - object: helmChartAdapter{&sourcev1b2.HelmChart{}}, - force: true, - } - - ns := obj.Spec.Chart.Spec.SourceRef.Namespace - if ns == "" { - ns = obj.Namespace + var ( + name string + ns string + ) + switch { + case obj.Spec.ChartRef != nil: + name, ns = obj.Spec.ChartRef.Name, obj.Spec.ChartRef.Namespace + if ns == "" { + ns = obj.Namespace + } + namespacedName := types.NamespacedName{ + Name: name, + Namespace: ns, + } + if obj.Spec.ChartRef.Kind == sourcev1.HelmChartKind { + return reconcileWithSourceCommand{ + apiType: helmChartType, + object: helmChartAdapter{&sourcev1.HelmChart{}}, + force: true, + }, namespacedName + } + return reconcileCommand{ + apiType: ociRepositoryType, + object: ociRepositoryAdapter{&sourcev1b2.OCIRepository{}}, + }, namespacedName + default: + // default case assumes the HelmRelease is using a HelmChartTemplate + ns = obj.Spec.Chart.Spec.SourceRef.Namespace + if ns == "" { + ns = obj.Namespace + } + name = fmt.Sprintf("%s-%s", obj.Namespace, obj.Name) + return reconcileWithSourceCommand{ + apiType: helmChartType, + object: helmChartAdapter{&sourcev1.HelmChart{}}, + force: true, + }, types.NamespacedName{ + Name: name, + Namespace: ns, + } } +} - return cmd, types.NamespacedName{ - Name: fmt.Sprintf("%s-%s", obj.Namespace, obj.Name), - Namespace: ns, - } +func (obj helmReleaseAdapter) isStatic() bool { + return false } diff --git a/cmd/flux/reconcile_image_repository.go b/cmd/flux/reconcile_image_repository.go index c7304eb7b1..6a386c8b54 100644 --- a/cmd/flux/reconcile_image_repository.go +++ b/cmd/flux/reconcile_image_repository.go @@ -48,3 +48,7 @@ func (obj imageRepositoryAdapter) lastHandledReconcileRequest() string { func (obj imageRepositoryAdapter) successMessage() string { return fmt.Sprintf("scan fetched %d tags", obj.Status.LastScanResult.TagCount) } + +func (obj imageRepositoryAdapter) isStatic() bool { + return false +} diff --git a/cmd/flux/reconcile_image_updateauto.go b/cmd/flux/reconcile_image_updateauto.go index 8fba13ccf4..95ec6e95a3 100644 --- a/cmd/flux/reconcile_image_updateauto.go +++ b/cmd/flux/reconcile_image_updateauto.go @@ -22,7 +22,7 @@ import ( "github.com/spf13/cobra" apimeta "k8s.io/apimachinery/pkg/api/meta" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" meta "github.com/fluxcd/pkg/apis/meta" ) @@ -43,10 +43,6 @@ func init() { reconcileImageCmd.AddCommand(reconcileImageUpdateCmd) } -func (obj imageUpdateAutomationAdapter) suspended() bool { - return obj.ImageUpdateAutomation.Spec.Suspend -} - func (obj imageUpdateAutomationAdapter) lastHandledReconcileRequest() string { return obj.Status.GetLastHandledReconcileRequest() } @@ -60,3 +56,7 @@ func (obj imageUpdateAutomationAdapter) successMessage() string { } return "automation not yet run" } + +func (obj imageUpdateAutomationAdapter) isStatic() bool { + return false +} diff --git a/cmd/flux/reconcile_kustomization.go b/cmd/flux/reconcile_kustomization.go index 99d1254512..7a18ec0264 100644 --- a/cmd/flux/reconcile_kustomization.go +++ b/cmd/flux/reconcile_kustomization.go @@ -88,3 +88,7 @@ func (obj kustomizationAdapter) getSource() (reconcileSource, types.NamespacedNa Namespace: obj.Spec.SourceRef.Namespace, } } + +func (obj kustomizationAdapter) isStatic() bool { + return false +} diff --git a/cmd/flux/reconcile_receiver.go b/cmd/flux/reconcile_receiver.go index 8a04ea7a81..1691a95da9 100644 --- a/cmd/flux/reconcile_receiver.go +++ b/cmd/flux/reconcile_receiver.go @@ -17,18 +17,9 @@ limitations under the License. package main import ( - "context" - "fmt" - "time" - "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" notificationv1 "github.com/fluxcd/notification-controller/api/v1" - "github.com/fluxcd/pkg/apis/meta" - - "github.com/fluxcd/flux2/v2/internal/utils" ) var reconcileReceiverCmd = &cobra.Command{ @@ -38,62 +29,20 @@ var reconcileReceiverCmd = &cobra.Command{ Example: ` # Trigger a reconciliation for an existing receiver flux reconcile receiver main`, ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)), - RunE: reconcileReceiverCmdRun, + RunE: reconcileCommand{ + apiType: receiverType, + object: receiverAdapter{¬ificationv1.Receiver{}}, + }.run, } func init() { reconcileCmd.AddCommand(reconcileReceiverCmd) } -func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return fmt.Errorf("receiver name is required") - } - name := args[0] - - ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) - defer cancel() - - kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) - if err != nil { - return err - } - - namespacedName := types.NamespacedName{ - Namespace: *kubeconfigArgs.Namespace, - Name: name, - } - - var receiver notificationv1.Receiver - err = kubeClient.Get(ctx, namespacedName, &receiver) - if err != nil { - return err - } - - if receiver.Spec.Suspend { - return fmt.Errorf("resource is suspended") - } - - logger.Actionf("annotating Receiver %s in %s namespace", name, *kubeconfigArgs.Namespace) - if receiver.Annotations == nil { - receiver.Annotations = map[string]string{ - meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), - } - } else { - receiver.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) - } - if err := kubeClient.Update(ctx, &receiver); err != nil { - return err - } - logger.Successf("Receiver annotated") - - logger.Waitingf("waiting for Receiver reconciliation") - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil { - return err - } - - logger.Successf("Receiver reconciliation completed") +func (obj receiverAdapter) lastHandledReconcileRequest() string { + return obj.Status.GetLastHandledReconcileRequest() +} - return nil +func (obj receiverAdapter) isStatic() bool { + return false } diff --git a/cmd/flux/reconcile_source_bucket.go b/cmd/flux/reconcile_source_bucket.go index 2a2bbdbc71..68079a666e 100644 --- a/cmd/flux/reconcile_source_bucket.go +++ b/cmd/flux/reconcile_source_bucket.go @@ -48,3 +48,7 @@ func (obj bucketAdapter) lastHandledReconcileRequest() string { func (obj bucketAdapter) successMessage() string { return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision) } + +func (obj bucketAdapter) isStatic() bool { + return false +} diff --git a/cmd/flux/reconcile_source_chart.go b/cmd/flux/reconcile_source_chart.go index 134b0a13f5..77887b7504 100644 --- a/cmd/flux/reconcile_source_chart.go +++ b/cmd/flux/reconcile_source_chart.go @@ -20,7 +20,6 @@ import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/types" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" ) @@ -34,10 +33,10 @@ var reconcileSourceHelmChartCmd = &cobra.Command{ # Trigger a reconciliation of the HelmCharts's source and apply changes flux reconcile helmchart podinfo --with-source`, - ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)), + ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)), RunE: reconcileWithSourceCommand{ apiType: helmChartType, - object: helmChartAdapter{&sourcev1b2.HelmChart{}}, + object: helmChartAdapter{&sourcev1.HelmChart{}}, }.run, } @@ -63,10 +62,10 @@ func (obj helmChartAdapter) reconcileSource() bool { func (obj helmChartAdapter) getSource() (reconcileSource, types.NamespacedName) { var cmd reconcileCommand switch obj.Spec.SourceRef.Kind { - case sourcev1b2.HelmRepositoryKind: + case sourcev1.HelmRepositoryKind: cmd = reconcileCommand{ apiType: helmRepositoryType, - object: helmRepositoryAdapter{&sourcev1b2.HelmRepository{}}, + object: helmRepositoryAdapter{&sourcev1.HelmRepository{}}, } case sourcev1.GitRepositoryKind: cmd = reconcileCommand{ @@ -85,3 +84,7 @@ func (obj helmChartAdapter) getSource() (reconcileSource, types.NamespacedName) Namespace: obj.Namespace, } } + +func (obj helmChartAdapter) isStatic() bool { + return false +} diff --git a/cmd/flux/reconcile_source_git.go b/cmd/flux/reconcile_source_git.go index a42cd7b959..282564961a 100644 --- a/cmd/flux/reconcile_source_git.go +++ b/cmd/flux/reconcile_source_git.go @@ -48,3 +48,7 @@ func (obj gitRepositoryAdapter) lastHandledReconcileRequest() string { func (obj gitRepositoryAdapter) successMessage() string { return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision) } + +func (obj gitRepositoryAdapter) isStatic() bool { + return false +} diff --git a/cmd/flux/reconcile_source_helm.go b/cmd/flux/reconcile_source_helm.go index f559a5e4c8..a9b89d3d8e 100644 --- a/cmd/flux/reconcile_source_helm.go +++ b/cmd/flux/reconcile_source_helm.go @@ -23,7 +23,7 @@ import ( "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" ) var reconcileSourceHelmCmd = &cobra.Command{ @@ -60,3 +60,7 @@ func (obj helmRepositoryAdapter) successMessage() string { } return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision) } + +func (obj helmRepositoryAdapter) isStatic() bool { + return obj.Spec.Type == sourcev1.HelmRepositoryTypeOCI +} diff --git a/cmd/flux/reconcile_source_oci.go b/cmd/flux/reconcile_source_oci.go index ffc649f9b3..52237f2f22 100644 --- a/cmd/flux/reconcile_source_oci.go +++ b/cmd/flux/reconcile_source_oci.go @@ -48,3 +48,7 @@ func (obj ociRepositoryAdapter) lastHandledReconcileRequest() string { func (obj ociRepositoryAdapter) successMessage() string { return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision) } + +func (obj ociRepositoryAdapter) isStatic() bool { + return false +} diff --git a/cmd/flux/reconcile_with_source.go b/cmd/flux/reconcile_with_source.go index d0c3ae7866..92d5b44579 100644 --- a/cmd/flux/reconcile_with_source.go +++ b/cmd/flux/reconcile_with_source.go @@ -10,8 +10,9 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" - "github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/pkg/apis/meta" + + "github.com/fluxcd/flux2/v2/internal/utils" ) type reconcileWithSource interface { @@ -82,8 +83,8 @@ func (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []strin logger.Successf("%s annotated", reconcile.kind) logger.Waitingf("waiting for %s reconciliation", reconcile.kind) - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil { + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, + reconciliationHandled(kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil { return err } diff --git a/cmd/flux/resume.go b/cmd/flux/resume.go index bc27c4ebc6..5f3c8b8145 100644 --- a/cmd/flux/resume.go +++ b/cmd/flux/resume.go @@ -56,6 +56,7 @@ type resumable interface { copyable statusable setUnsuspended() + isStatic() bool successMessage() string } @@ -212,8 +213,12 @@ func (resume resumeCommand) reconcile(ctx context.Context, res resumable) reconc logger.Waitingf("waiting for %s reconciliation", resume.kind) - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isReady(ctx, resume.client, namespacedName, res)); err != nil { + readyConditionFunc := isObjectReadyConditionFunc(resume.client, namespacedName, res.asClientObject()) + if res.isStatic() { + readyConditionFunc = isStaticObjectReadyConditionFunc(resume.client, namespacedName, res.asClientObject()) + } + + if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true, readyConditionFunc); err != nil { return reconcileResponse{ resumable: res, err: err, diff --git a/cmd/flux/resume_alert.go b/cmd/flux/resume_alert.go index 713aebb38a..a33d4f5b0e 100644 --- a/cmd/flux/resume_alert.go +++ b/cmd/flux/resume_alert.go @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" ) var resumeAlertCmd = &cobra.Command{ @@ -44,7 +44,7 @@ func init() { } func (obj alertAdapter) getObservedGeneration() int64 { - return obj.Alert.Status.ObservedGeneration + return 0 } func (obj alertAdapter) setUnsuspended() { @@ -55,6 +55,10 @@ func (obj alertAdapter) successMessage() string { return "Alert reconciliation completed" } +func (a alertAdapter) isStatic() bool { + return true +} + func (a alertListAdapter) resumeItem(i int) resumable { return &alertAdapter{&a.AlertList.Items[i]} } diff --git a/cmd/flux/resume_alertprovider.go b/cmd/flux/resume_alertprovider.go new file mode 100644 index 0000000000..de95d48d0d --- /dev/null +++ b/cmd/flux/resume_alertprovider.go @@ -0,0 +1,64 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" +) + +var resumeAlertProviderCmd = &cobra.Command{ + Use: "alert-provider [name]", + Short: "Resume a suspended Provider", + Long: `The resume command marks a previously suspended Provider resource for reconciliation and waits for it to +finish the apply.`, + Example: ` # Resume reconciliation for an existing Provider + flux resume alert-provider main + + # Resume reconciliation for multiple Providers + flux resume alert-provider main-1 main-2`, + ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)), + RunE: resumeCommand{ + apiType: alertProviderType, + list: &alertProviderListAdapter{¬ificationv1.ProviderList{}}, + }.run, +} + +func init() { + resumeCmd.AddCommand(resumeAlertProviderCmd) +} + +func (obj alertProviderAdapter) getObservedGeneration() int64 { + return 0 +} + +func (obj alertProviderAdapter) setUnsuspended() { + obj.Provider.Spec.Suspend = false +} + +func (obj alertProviderAdapter) successMessage() string { + return "Provider reconciliation completed" +} + +func (a alertProviderAdapter) isStatic() bool { + return true +} + +func (a alertProviderListAdapter) resumeItem(i int) resumable { + return &alertProviderAdapter{&a.ProviderList.Items[i]} +} diff --git a/cmd/flux/resume_helmrelease.go b/cmd/flux/resume_helmrelease.go index b058d273b4..6c320b3056 100644 --- a/cmd/flux/resume_helmrelease.go +++ b/cmd/flux/resume_helmrelease.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Flux authors +Copyright 2024 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import ( "github.com/spf13/cobra" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" ) var resumeHrCmd = &cobra.Command{ @@ -55,7 +55,7 @@ func (obj helmReleaseAdapter) setUnsuspended() { } func (obj helmReleaseAdapter) successMessage() string { - return fmt.Sprintf("applied revision %s", obj.Status.LastAppliedRevision) + return fmt.Sprintf("applied revision %s", getHelmReleaseRevision(*obj.HelmRelease)) } func (a helmReleaseListAdapter) resumeItem(i int) resumable { diff --git a/cmd/flux/resume_image_updateauto.go b/cmd/flux/resume_image_updateauto.go index d50fddf787..1b29cbafd9 100644 --- a/cmd/flux/resume_image_updateauto.go +++ b/cmd/flux/resume_image_updateauto.go @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" ) var resumeImageUpdateCmd = &cobra.Command{ diff --git a/cmd/flux/resume_source_chart.go b/cmd/flux/resume_source_chart.go index 6322f06fc1..9e7ec7591b 100644 --- a/cmd/flux/resume_source_chart.go +++ b/cmd/flux/resume_source_chart.go @@ -21,7 +21,7 @@ import ( "github.com/spf13/cobra" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" ) var resumeSourceHelmChartCmd = &cobra.Command{ diff --git a/cmd/flux/resume_source_helm.go b/cmd/flux/resume_source_helm.go index 0ac641ebb6..b4cb13164e 100644 --- a/cmd/flux/resume_source_helm.go +++ b/cmd/flux/resume_source_helm.go @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" ) var resumeSourceHelmCmd = &cobra.Command{ diff --git a/cmd/flux/source.go b/cmd/flux/source.go index 7dc96fdf1d..64f755be3b 100644 --- a/cmd/flux/source.go +++ b/cmd/flux/source.go @@ -95,16 +95,16 @@ func (a bucketListAdapter) len() int { return len(a.BucketList.Items) } -// sourcev1b2.HelmChart +// sourcev1.HelmChart var helmChartType = apiType{ - kind: sourcev1b2.HelmChartKind, + kind: sourcev1.HelmChartKind, humanKind: "source chart", - groupVersion: sourcev1b2.GroupVersion, + groupVersion: sourcev1.GroupVersion, } type helmChartAdapter struct { - *sourcev1b2.HelmChart + *sourcev1.HelmChart } func (a helmChartAdapter) asClientObject() client.Object { @@ -115,10 +115,10 @@ func (a helmChartAdapter) deepCopyClientObject() client.Object { return a.HelmChart.DeepCopy() } -// sourcev1b2.HelmChartList +// sourcev1.HelmChartList type helmChartListAdapter struct { - *sourcev1b2.HelmChartList + *sourcev1.HelmChartList } func (a helmChartListAdapter) asClientList() client.ObjectList { @@ -163,16 +163,16 @@ func (a gitRepositoryListAdapter) len() int { return len(a.GitRepositoryList.Items) } -// sourcev1b2.HelmRepository +// sourcev1.HelmRepository var helmRepositoryType = apiType{ - kind: sourcev1b2.HelmRepositoryKind, + kind: sourcev1.HelmRepositoryKind, humanKind: "source helm", - groupVersion: sourcev1b2.GroupVersion, + groupVersion: sourcev1.GroupVersion, } type helmRepositoryAdapter struct { - *sourcev1b2.HelmRepository + *sourcev1.HelmRepository } func (a helmRepositoryAdapter) asClientObject() client.Object { @@ -183,10 +183,10 @@ func (a helmRepositoryAdapter) deepCopyClientObject() client.Object { return a.HelmRepository.DeepCopy() } -// sourcev1b2.HelmRepositoryList +// sourcev1.HelmRepositoryList type helmRepositoryListAdapter struct { - *sourcev1b2.HelmRepositoryList + *sourcev1.HelmRepositoryList } func (a helmRepositoryListAdapter) asClientList() client.ObjectList { diff --git a/cmd/flux/stats.go b/cmd/flux/stats.go index 25e35c554f..08ef3294a9 100644 --- a/cmd/flux/stats.go +++ b/cmd/flux/stats.go @@ -24,23 +24,25 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/fluxcd/flux2/v2/internal/utils" - "github.com/fluxcd/flux2/v2/pkg/printers" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + helmv2 "github.com/fluxcd/helm-controller/api/v2" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" notificationv1 "github.com/fluxcd/notification-controller/api/v1" - notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3" sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" + + "github.com/fluxcd/flux2/v2/internal/utils" + "github.com/fluxcd/flux2/v2/pkg/printers" ) var statsCmd = &cobra.Command{ Use: "stats", + Args: cobra.NoArgs, Short: "Stats of Flux reconciles", Long: withPreviewNote(`The stats command prints a report of Flux custom resources present on a cluster, including their reconcile status and the amount of cumulative storage used for each source type`), @@ -85,14 +87,14 @@ func runStatsCmd(cmd *cobra.Command, args []string) error { Group: sourcev1b2.GroupVersion.Group, }, { - Kind: sourcev1b2.HelmRepositoryKind, - Version: sourcev1b2.GroupVersion.Version, - Group: sourcev1b2.GroupVersion.Group, + Kind: sourcev1.HelmRepositoryKind, + Version: sourcev1.GroupVersion.Version, + Group: sourcev1.GroupVersion.Group, }, { - Kind: sourcev1b2.HelmChartKind, - Version: sourcev1b2.GroupVersion.Version, - Group: sourcev1b2.GroupVersion.Group, + Kind: sourcev1.HelmChartKind, + Version: sourcev1.GroupVersion.Version, + Group: sourcev1.GroupVersion.Group, }, { Kind: sourcev1b2.BucketKind, @@ -110,14 +112,14 @@ func runStatsCmd(cmd *cobra.Command, args []string) error { Group: helmv2.GroupVersion.Group, }, { - Kind: notificationv1b2.AlertKind, - Version: notificationv1b2.GroupVersion.Version, - Group: notificationv1b2.GroupVersion.Group, + Kind: notificationv1b3.AlertKind, + Version: notificationv1b3.GroupVersion.Version, + Group: notificationv1b3.GroupVersion.Group, }, { - Kind: notificationv1b2.ProviderKind, - Version: notificationv1b2.GroupVersion.Version, - Group: notificationv1b2.GroupVersion.Group, + Kind: notificationv1b3.ProviderKind, + Version: notificationv1b3.GroupVersion.Version, + Group: notificationv1b3.GroupVersion.Group, }, { Kind: notificationv1.ReceiverKind, diff --git a/cmd/flux/status.go b/cmd/flux/status.go index e6c4b5fc65..95c6e9f32f 100644 --- a/cmd/flux/status.go +++ b/cmd/flux/status.go @@ -17,17 +17,9 @@ limitations under the License. package main import ( - "context" - "fmt" - - "github.com/fluxcd/pkg/apis/meta" - apimeta "k8s.io/apimachinery/pkg/api/meta" + "github.com/fluxcd/cli-utils/pkg/object" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - "sigs.k8s.io/cli-utils/pkg/object" - "sigs.k8s.io/controller-runtime/pkg/client" ) // statusable is used to see if a resource is considered ready in the usual way @@ -44,43 +36,6 @@ type oldConditions interface { GetStatusConditions() *[]metav1.Condition } -func statusableConditions(object statusable) []metav1.Condition { - if s, ok := object.(meta.ObjectWithConditions); ok { - return s.GetConditions() - } - - if s, ok := object.(oldConditions); ok { - return *s.GetStatusConditions() - } - - return []metav1.Condition{} -} - -func isReady(ctx context.Context, kubeClient client.Client, - namespacedName types.NamespacedName, object statusable) wait.ConditionFunc { - return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, object.asClientObject()) - if err != nil { - return false, err - } - - // Confirm the state we are observing is for the current generation - if object.GetGeneration() != object.getObservedGeneration() { - return false, nil - } - - if c := apimeta.FindStatusCondition(statusableConditions(object), meta.ReadyCondition); c != nil { - switch c.Status { - case metav1.ConditionTrue: - return true, nil - case metav1.ConditionFalse: - return false, fmt.Errorf(c.Message) - } - } - return false, nil - } -} - func buildComponentObjectRefs(components ...string) ([]object.ObjMetadata, error) { var objRefs []object.ObjMetadata for _, deployment := range components { diff --git a/cmd/flux/suspend_alert.go b/cmd/flux/suspend_alert.go index 17cdd80187..67fa201a38 100644 --- a/cmd/flux/suspend_alert.go +++ b/cmd/flux/suspend_alert.go @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" ) var suspendAlertCmd = &cobra.Command{ diff --git a/cmd/flux/suspend_alertprovider.go b/cmd/flux/suspend_alertprovider.go new file mode 100644 index 0000000000..fa58c80eb8 --- /dev/null +++ b/cmd/flux/suspend_alertprovider.go @@ -0,0 +1,56 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta3" +) + +var suspendAlertProviderCmd = &cobra.Command{ + Use: "alert-provider [name]", + Short: "Suspend reconciliation of Provider", + Long: `The suspend command disables the reconciliation of a Provider resource.`, + Example: ` # Suspend reconciliation for an existing Provider + flux suspend alert-provider main + + # Suspend reconciliation for multiple Providers + flux suspend alert-providers main-1 main-2`, + ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)), + RunE: suspendCommand{ + apiType: alertProviderType, + object: &alertProviderAdapter{¬ificationv1.Provider{}}, + list: &alertProviderListAdapter{¬ificationv1.ProviderList{}}, + }.run, +} + +func init() { + suspendCmd.AddCommand(suspendAlertProviderCmd) +} + +func (obj alertProviderAdapter) isSuspended() bool { + return obj.Provider.Spec.Suspend +} + +func (obj alertProviderAdapter) setSuspended() { + obj.Provider.Spec.Suspend = true +} + +func (a alertProviderListAdapter) item(i int) suspendable { + return &alertProviderAdapter{&a.ProviderList.Items[i]} +} diff --git a/cmd/flux/suspend_helmrelease.go b/cmd/flux/suspend_helmrelease.go index 5872c8b464..762e70ebdc 100644 --- a/cmd/flux/suspend_helmrelease.go +++ b/cmd/flux/suspend_helmrelease.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Flux authors +Copyright 2024 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" ) var suspendHrCmd = &cobra.Command{ diff --git a/cmd/flux/suspend_image_updateauto.go b/cmd/flux/suspend_image_updateauto.go index 1c8f11ef6c..a10351174e 100644 --- a/cmd/flux/suspend_image_updateauto.go +++ b/cmd/flux/suspend_image_updateauto.go @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" ) var suspendImageUpdateCmd = &cobra.Command{ diff --git a/cmd/flux/suspend_source_chart.go b/cmd/flux/suspend_source_chart.go index e91327e844..6da1d8c0c6 100644 --- a/cmd/flux/suspend_source_chart.go +++ b/cmd/flux/suspend_source_chart.go @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" ) var suspendSourceHelmChartCmd = &cobra.Command{ diff --git a/cmd/flux/suspend_source_helm.go b/cmd/flux/suspend_source_helm.go index 22598c64cf..a59343bd49 100644 --- a/cmd/flux/suspend_source_helm.go +++ b/cmd/flux/suspend_source_helm.go @@ -19,7 +19,7 @@ package main import ( "github.com/spf13/cobra" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" ) var suspendSourceHelmCmd = &cobra.Command{ diff --git a/cmd/flux/testdata/check/check_pre.golden b/cmd/flux/testdata/check/check_pre.golden index 4a263acdcf..312f6861ab 100644 --- a/cmd/flux/testdata/check/check_pre.golden +++ b/cmd/flux/testdata/check/check_pre.golden @@ -1,3 +1,3 @@ â–º checking prerequisites -✔ Kubernetes {{ .serverVersion }} >=1.24.0-0 +✔ Kubernetes {{ .serverVersion }} >=1.28.0-0 ✔ prerequisites checks passed diff --git a/cmd/flux/testdata/cluster_info/gitrepositories.yaml b/cmd/flux/testdata/cluster_info/gitrepositories.yaml new file mode 100644 index 0000000000..3e4ba5c736 --- /dev/null +++ b/cmd/flux/testdata/cluster_info/gitrepositories.yaml @@ -0,0 +1,424 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: gitrepositories.source.toolkit.fluxcd.io +spec: + group: source.toolkit.fluxcd.io + names: + kind: GitRepository + listKind: GitRepositoryList + plural: gitrepositories + shortNames: + - gitrepo + singular: gitrepository + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.url + name: URL + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + name: v1 + schema: + openAPIV3Schema: + description: GitRepository is the Schema for the gitrepositories API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GitRepositorySpec specifies the required configuration to + produce an Artifact for a Git repository. + properties: + ignore: + description: Ignore overrides the set of excluded patterns in the + .sourceignore format (which is the same as .gitignore). If not provided, + a default will be used, consult the documentation for your version + to find out what those are. + type: string + include: + description: Include specifies a list of GitRepository resources which + Artifacts should be included in the Artifact produced for this GitRepository. + items: + description: GitRepositoryInclude specifies a local reference to + a GitRepository which Artifact (sub-)contents must be included, + and where they should be placed. + properties: + fromPath: + description: FromPath specifies the path to copy contents from, + defaults to the root of the Artifact. + type: string + repository: + description: GitRepositoryRef specifies the GitRepository which + Artifact contents must be included. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + toPath: + description: ToPath specifies the path to copy contents to, + defaults to the name of the GitRepositoryRef. + type: string + required: + - repository + type: object + type: array + interval: + description: Interval at which the GitRepository URL is checked for + updates. This interval is approximate and may be subject to jitter + to ensure efficient use of resources. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + proxySecretRef: + description: ProxySecretRef specifies the Secret containing the proxy + configuration to use while communicating with the Git server. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + recurseSubmodules: + description: RecurseSubmodules enables the initialization of all submodules + within the GitRepository as cloned from the URL, using their default + settings. + type: boolean + ref: + description: Reference specifies the Git reference to resolve and + monitor for changes, defaults to the 'master' branch. + properties: + branch: + description: Branch to check out, defaults to 'master' if no other + field is defined. + type: string + commit: + description: "Commit SHA to check out, takes precedence over all + reference fields. \n This can be combined with Branch to shallow + clone the branch, in which the commit is expected to exist." + type: string + name: + description: "Name of the reference to check out; takes precedence + over Branch, Tag and SemVer. \n It must be a valid Git reference: + https://git-scm.com/docs/git-check-ref-format#_description Examples: + \"refs/heads/main\", \"refs/tags/v0.1.0\", \"refs/pull/420/head\", + \"refs/merge-requests/1/head\"" + type: string + semver: + description: SemVer tag expression to check out, takes precedence + over Tag. + type: string + tag: + description: Tag to check out, takes precedence over Branch. + type: string + type: object + secretRef: + description: SecretRef specifies the Secret containing authentication + credentials for the GitRepository. For HTTPS repositories the Secret + must contain 'username' and 'password' fields for basic auth or + 'bearerToken' field for token auth. For SSH repositories the Secret + must contain 'identity' and 'known_hosts' fields. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + suspend: + description: Suspend tells the controller to suspend the reconciliation + of this GitRepository. + type: boolean + timeout: + default: 60s + description: Timeout for Git operations like cloning, defaults to + 60s. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ + type: string + url: + description: URL specifies the Git repository URL, it can be an HTTP/S + or SSH address. + pattern: ^(http|https|ssh)://.*$ + type: string + verify: + description: Verification specifies the configuration to verify the + Git commit signature(s). + properties: + mode: + default: HEAD + description: "Mode specifies which Git object(s) should be verified. + \n The variants \"head\" and \"HEAD\" both imply the same thing, + i.e. verify the commit that the HEAD of the Git repository points + to. The variant \"head\" solely exists to ensure backwards compatibility." + enum: + - head + - HEAD + - Tag + - TagAndHEAD + type: string + secretRef: + description: SecretRef specifies the Secret containing the public + keys of trusted Git authors. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + required: + - secretRef + type: object + required: + - interval + - url + type: object + status: + default: + observedGeneration: -1 + description: GitRepositoryStatus records the observed state of a Git repository. + properties: + artifact: + description: Artifact represents the last successful GitRepository + reconciliation. + properties: + digest: + description: Digest is the digest of the file in the form of ':'. + pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of the Artifact. + format: date-time + type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI annotations. + type: object + path: + description: Path is the relative file path of the Artifact. It + can be used to locate the file in the root of the Artifact storage + on the local file system of the controller managing the Source. + type: string + revision: + description: Revision is a human-readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm chart version, etc. + type: string + size: + description: Size is the number of bytes in the file. + format: int64 + type: integer + url: + description: URL is the HTTP address of the Artifact as exposed + by the controller managing the Source. It can be used to retrieve + the Artifact for consumption, e.g. by another controller applying + the Artifact contents. + type: string + required: + - lastUpdateTime + - path + - revision + - url + type: object + conditions: + description: Conditions holds the conditions for the GitRepository. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + 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])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + includedArtifacts: + description: IncludedArtifacts contains a list of the last successfully + included Artifacts as instructed by GitRepositorySpec.Include. + items: + description: Artifact represents the output of a Source reconciliation. + properties: + digest: + description: Digest is the digest of the file in the form of + ':'. + pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of the Artifact. + format: date-time + type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI + annotations. + type: object + path: + description: Path is the relative file path of the Artifact. + It can be used to locate the file in the root of the Artifact + storage on the local file system of the controller managing + the Source. + type: string + revision: + description: Revision is a human-readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm chart version, etc. + type: string + size: + description: Size is the number of bytes in the file. + format: int64 + type: integer + url: + description: URL is the HTTP address of the Artifact as exposed + by the controller managing the Source. It can be used to retrieve + the Artifact for consumption, e.g. by another controller applying + the Artifact contents. + type: string + required: + - lastUpdateTime + - path + - revision + - url + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation of + the GitRepository object. + format: int64 + type: integer + observedIgnore: + description: ObservedIgnore is the observed exclusion patterns used + for constructing the source artifact. + type: string + observedInclude: + description: ObservedInclude is the observed list of GitRepository + resources used to produce the current Artifact. + items: + description: GitRepositoryInclude specifies a local reference to + a GitRepository which Artifact (sub-)contents must be included, + and where they should be placed. + properties: + fromPath: + description: FromPath specifies the path to copy contents from, + defaults to the root of the Artifact. + type: string + repository: + description: GitRepositoryRef specifies the GitRepository which + Artifact contents must be included. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + toPath: + description: ToPath specifies the path to copy contents to, + defaults to the name of the GitRepositoryRef. + type: string + required: + - repository + type: object + type: array + observedRecurseSubmodules: + description: ObservedRecurseSubmodules is the observed resource submodules + configuration used to produce the current Artifact. + type: boolean + sourceVerificationMode: + description: SourceVerificationMode is the last used verification + mode indicating which Git object(s) have been verified. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/cmd/flux/testdata/create_hr/basic.yaml b/cmd/flux/testdata/create_hr/basic.yaml new file mode 100644 index 0000000000..066b1610ee --- /dev/null +++ b/cmd/flux/testdata/create_hr/basic.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + chart: + spec: + chart: podinfo + reconcileStrategy: ChartVersion + sourceRef: + kind: HelmRepository + name: podinfo + interval: 1m0s diff --git a/cmd/flux/testdata/create_hr/hc_basic.yaml b/cmd/flux/testdata/create_hr/hc_basic.yaml new file mode 100644 index 0000000000..6e99f5d05c --- /dev/null +++ b/cmd/flux/testdata/create_hr/hc_basic.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + chartRef: + kind: HelmChart + name: podinfo + interval: 1m0s diff --git a/cmd/flux/testdata/create_hr/or_basic.yaml b/cmd/flux/testdata/create_hr/or_basic.yaml new file mode 100644 index 0000000000..368027a21b --- /dev/null +++ b/cmd/flux/testdata/create_hr/or_basic.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + chartRef: + kind: OCIRepository + name: podinfo + interval: 1m0s diff --git a/cmd/flux/testdata/create_hr/setup-source.yaml b/cmd/flux/testdata/create_hr/setup-source.yaml new file mode 100644 index 0000000000..254024a043 --- /dev/null +++ b/cmd/flux/testdata/create_hr/setup-source.yaml @@ -0,0 +1,39 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .fluxns }} +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + interval: 1m0s + provider: generic + type: oci + url: oci://ghcr.io/stefanprodan/charts +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmChart +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + interval: 1m0s + chart: podinfo + sourceRef: + kind: HelmRepository + name: podinfo +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 10m + url: oci://ghcr.io/stefanprodan/manifests/podinfo + ref: + tag: latest diff --git a/cmd/flux/testdata/create_secret/git/secret-ca-crt.yaml b/cmd/flux/testdata/create_secret/git/secret-ca-crt.yaml new file mode 100644 index 0000000000..958bd45fc1 --- /dev/null +++ b/cmd/flux/testdata/create_secret/git/secret-ca-crt.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: ca-crt + namespace: my-namespace +stringData: + ca.crt: ca-data + password: my-password + username: my-username + diff --git a/cmd/flux/testdata/create_secret/notation/invalid-trust-policy.json b/cmd/flux/testdata/create_secret/notation/invalid-trust-policy.json new file mode 100644 index 0000000000..257b772f36 --- /dev/null +++ b/cmd/flux/testdata/create_secret/notation/invalid-trust-policy.json @@ -0,0 +1,4 @@ +{ + "version": "1.0", + "trustPolicies": [{}] +} diff --git a/cmd/flux/testdata/create_secret/notation/invalid.json b/cmd/flux/testdata/create_secret/notation/invalid.json new file mode 100644 index 0000000000..e16c76dff8 --- /dev/null +++ b/cmd/flux/testdata/create_secret/notation/invalid.json @@ -0,0 +1 @@ +"" diff --git a/cmd/flux/testdata/create_secret/notation/secret-ca-crt.yaml b/cmd/flux/testdata/create_secret/notation/secret-ca-crt.yaml new file mode 100644 index 0000000000..28f37c085d --- /dev/null +++ b/cmd/flux/testdata/create_secret/notation/secret-ca-crt.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: notation-config + namespace: my-namespace +stringData: + ca.crt: ca-data-crt + trustpolicy.json: | + { + "version": "1.0", + "trustPolicies": [ + { + "name": "fluxcd.io", + "registryScopes": [ + "*" + ], + "signatureVerification": { + "level" : "strict" + }, + "trustStores": [ "ca:fluxcd.io" ], + "trustedIdentities": [ + "*" + ] + } + ] + } + diff --git a/cmd/flux/testdata/create_secret/notation/secret-ca-multi.yaml b/cmd/flux/testdata/create_secret/notation/secret-ca-multi.yaml new file mode 100644 index 0000000000..80652de759 --- /dev/null +++ b/cmd/flux/testdata/create_secret/notation/secret-ca-multi.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: notation-config + namespace: my-namespace +stringData: + ca.crt: ca-data-crt + ca.pem: ca-data-pem + trustpolicy.json: | + { + "version": "1.0", + "trustPolicies": [ + { + "name": "fluxcd.io", + "registryScopes": [ + "*" + ], + "signatureVerification": { + "level" : "strict" + }, + "trustStores": [ "ca:fluxcd.io" ], + "trustedIdentities": [ + "*" + ] + } + ] + } + diff --git a/cmd/flux/testdata/create_secret/notation/secret-ca-pem.yaml b/cmd/flux/testdata/create_secret/notation/secret-ca-pem.yaml new file mode 100644 index 0000000000..e206809b3e --- /dev/null +++ b/cmd/flux/testdata/create_secret/notation/secret-ca-pem.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: notation-config + namespace: my-namespace +stringData: + ca.pem: ca-data-pem + trustpolicy.json: | + { + "version": "1.0", + "trustPolicies": [ + { + "name": "fluxcd.io", + "registryScopes": [ + "*" + ], + "signatureVerification": { + "level" : "strict" + }, + "trustStores": [ "ca:fluxcd.io" ], + "trustedIdentities": [ + "*" + ] + } + ] + } + diff --git a/cmd/flux/testdata/create_secret/notation/test-ca.crt b/cmd/flux/testdata/create_secret/notation/test-ca.crt new file mode 100644 index 0000000000..e1ad1da4a8 --- /dev/null +++ b/cmd/flux/testdata/create_secret/notation/test-ca.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbDCCAlSgAwIBAgIUP7zhmTw5XTWLcgBGkBEsErMOkz4wDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCUk8xCzAJBgNVBAgMAkJVMRIwEAYDVQQHDAlCdWNoYXJl +c3QxDzANBgNVBAoMBk5vdGFyeTEZMBcGA1UEAwwQc3RlZmFucHJvZGFuLmNvbTAe +Fw0yNDAyMjUxMDAyMzZaFw0yOTAyMjQxMDAyMzZaMFoxCzAJBgNVBAYTAlJPMQsw +CQYDVQQIDAJCVTESMBAGA1UEBwwJQnVjaGFyZXN0MQ8wDQYDVQQKDAZOb3Rhcnkx +GTAXBgNVBAMMEHN0ZWZhbnByb2Rhbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDtH4oPi3SyX/DGv6NdjIvmApvD9eeSgsmHdwpAly8T9D2me+fx +Z+wRNJmq4aq/A1anX+Sg28iwHzV+1WKpsHnjYzDAJSEYP2S8A5H1nGRKUoibdijw +C3QBh5C75rjF/tmZVSX/Vgbf3HJJEsF4WUxWabLxoV2QLo7UlEsQd9+bSeKNMncx +1+E6FdbRCrYo90iobvZJ8K/S2zCWq/JTeHfTnmSEDhx6nMJcaSjvMPn3zyauWcQw +dDpkcaGiJ64fEJRT2OFxXv9u+vDmIMKzo/Wjbd+IzFj6YY4VisK88aU7tmDelnk5 +gQB9eu62PFoaVsYJp4VOhblFKvGJpQwbWB9BAgMBAAGjKjAoMA4GA1UdDwEB/wQE +AwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEA +6x+C6hAIbLwMvkNx4K5p7Qe/pLQR0VwQFAw10yr/5KSN+YKFpon6pQ0TebL7qll+ +uBGZvtQhN6v+DlnVqB7lvJKd+89isgirkkews5KwuXg7Gv5UPIugH0dXISZU8DMJ +7J4oKREv5HzdFmfsUfNlQcfyVTjKL6UINXfKGdqNNxXxR9b4a1TY2JcmEhzBTHaq +ZqX6HK784a0dB7aHgeFrFwPCCP4M684Hs7CFbk3jo2Ef4ljnB5AyWpe8pwCLMdRt +UjSjL5xJWVQvRU+STQsPr6SvpokPCG4rLQyjgeYYk4CCj5piSxbSUZFavq8v1y7Y +m91USVqfeUX7ZzjDxPHE2A== +-----END CERTIFICATE----- diff --git a/cmd/flux/testdata/create_secret/notation/test-ca2.crt b/cmd/flux/testdata/create_secret/notation/test-ca2.crt new file mode 100644 index 0000000000..e1ad1da4a8 --- /dev/null +++ b/cmd/flux/testdata/create_secret/notation/test-ca2.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbDCCAlSgAwIBAgIUP7zhmTw5XTWLcgBGkBEsErMOkz4wDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCUk8xCzAJBgNVBAgMAkJVMRIwEAYDVQQHDAlCdWNoYXJl +c3QxDzANBgNVBAoMBk5vdGFyeTEZMBcGA1UEAwwQc3RlZmFucHJvZGFuLmNvbTAe +Fw0yNDAyMjUxMDAyMzZaFw0yOTAyMjQxMDAyMzZaMFoxCzAJBgNVBAYTAlJPMQsw +CQYDVQQIDAJCVTESMBAGA1UEBwwJQnVjaGFyZXN0MQ8wDQYDVQQKDAZOb3Rhcnkx +GTAXBgNVBAMMEHN0ZWZhbnByb2Rhbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDtH4oPi3SyX/DGv6NdjIvmApvD9eeSgsmHdwpAly8T9D2me+fx +Z+wRNJmq4aq/A1anX+Sg28iwHzV+1WKpsHnjYzDAJSEYP2S8A5H1nGRKUoibdijw +C3QBh5C75rjF/tmZVSX/Vgbf3HJJEsF4WUxWabLxoV2QLo7UlEsQd9+bSeKNMncx +1+E6FdbRCrYo90iobvZJ8K/S2zCWq/JTeHfTnmSEDhx6nMJcaSjvMPn3zyauWcQw +dDpkcaGiJ64fEJRT2OFxXv9u+vDmIMKzo/Wjbd+IzFj6YY4VisK88aU7tmDelnk5 +gQB9eu62PFoaVsYJp4VOhblFKvGJpQwbWB9BAgMBAAGjKjAoMA4GA1UdDwEB/wQE +AwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEA +6x+C6hAIbLwMvkNx4K5p7Qe/pLQR0VwQFAw10yr/5KSN+YKFpon6pQ0TebL7qll+ +uBGZvtQhN6v+DlnVqB7lvJKd+89isgirkkews5KwuXg7Gv5UPIugH0dXISZU8DMJ +7J4oKREv5HzdFmfsUfNlQcfyVTjKL6UINXfKGdqNNxXxR9b4a1TY2JcmEhzBTHaq +ZqX6HK784a0dB7aHgeFrFwPCCP4M684Hs7CFbk3jo2Ef4ljnB5AyWpe8pwCLMdRt +UjSjL5xJWVQvRU+STQsPr6SvpokPCG4rLQyjgeYYk4CCj5piSxbSUZFavq8v1y7Y +m91USVqfeUX7ZzjDxPHE2A== +-----END CERTIFICATE----- diff --git a/cmd/flux/testdata/create_secret/notation/test-trust-policy.json b/cmd/flux/testdata/create_secret/notation/test-trust-policy.json new file mode 100644 index 0000000000..998c6dce04 --- /dev/null +++ b/cmd/flux/testdata/create_secret/notation/test-trust-policy.json @@ -0,0 +1,18 @@ +{ + "version": "1.0", + "trustPolicies": [ + { + "name": "fluxcd.io", + "registryScopes": [ + "*" + ], + "signatureVerification": { + "level" : "strict" + }, + "trustStores": [ "ca:fluxcd.io" ], + "trustedIdentities": [ + "*" + ] + } + ] +} diff --git a/cmd/flux/testdata/create_secret/tls/deprecated-secret-tls.yaml b/cmd/flux/testdata/create_secret/tls/deprecated-secret-tls.yaml new file mode 100644 index 0000000000..04e01d63ba --- /dev/null +++ b/cmd/flux/testdata/create_secret/tls/deprecated-secret-tls.yaml @@ -0,0 +1,107 @@ +Flag --cert-file has been deprecated, please use --tls-crt-file instead +Flag --key-file has been deprecated, please use --tls-key-file instead +Flag --ca-file has been deprecated, please use --ca-crt-file instead +--- +apiVersion: v1 +kind: Secret +metadata: + name: certs + namespace: my-namespace +stringData: + caFile: | + -----BEGIN CERTIFICATE----- + MIIBhzCCAS2gAwIBAgIUdsAtiX3gN0uk7ddxASWYE/tdv0wwCgYIKoZIzj0EAwIw + GTEXMBUGA1UEAxMOZXhhbXBsZS5jb20gQ0EwHhcNMjAwNDE3MDgxODAwWhcNMjUw + NDE2MDgxODAwWjAZMRcwFQYDVQQDEw5leGFtcGxlLmNvbSBDQTBZMBMGByqGSM49 + AgEGCCqGSM49AwEHA0IABK7h/5D8bV93MmEdhu02JsS6ugB8s6PzRl3PV4xs3Sbr + RNkkM59+x3b0iWx/i76qPYpNLoiVUVXQmA9Y+4DbMxijUzBRMA4GA1UdDwEB/wQE + AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQGyUiU1QEZiMAqjsnIYTwZ + 4yp5wzAPBgNVHREECDAGhwR/AAABMAoGCCqGSM49BAMCA0gAMEUCIQDzdtvKdE8O + 1+WRTZ9MuSiFYcrEz7Zne7VXouDEKqKEigIgM4WlbDeuNCKbqhqj+xZV0pa3rweb + OD8EjjCMY69RMO0= + -----END CERTIFICATE----- + certFile: | + -----BEGIN CERTIFICATE----- + MIIFazCCA1OgAwIBAgIUT84jeO/ncOrqI+FY05Fzbg8Ed7MwDQYJKoZIhvcNAQEL + BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTA4MDgxNDQyMzVaFw0yMjA4 + MDgxNDQyMzVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw + HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB + AQUAA4ICDwAwggIKAoICAQDn/rPsZ74oypiwCzLlx57zplTiCi/WLSF+MmLGuTvM + EQnV+OND2zFgvDIV/vFs3brkd6rLVI4NcdgSj4YKULCMwwOl45hQPdCTEPJvUhCm + M+FuQ0czmEEJSjZtdLFz1B7QB/JemNnbfigxM9mlg58AlBhVJqn8q64wd/kC/W/K + JTLJuBiVf12ZiPoPfO4WSxAqD3opZ8gdbmK0KYQAhKjEto6ZrYGisfwU1gt3l8M7 + sCJSpEkOkpuQgJ8D+xzJS36VXBJQMMP9nAPps+x/rGFplsPMsXEFFiwvR1+FJZwz + lg2sJ91bLGZQ7vn74MfsGrxpiJwllRThJyT7C9V0sjb5trT2lEqZlP2dRSJYt7aJ + 1crEcdGSl6RIKgxSV6Hk8dh/ZaTjrTwaKxVkPo2IeEXy5xrR7DyonOQ6Yes0KOCm + JB5yHkFlIVEnLm/HZXEtm3bPHsFgTZuInyBCOMXpUESuVZIw8YK+Vd6AExGPPwZ4 + n5I/sCDxWII9owIj3LeLzdUG6JoroahhGmo8rgpbJpPnS+VgryQ/raUQjqDzDCuE + 9vKXKBlSUqK6H9A+NMc0mme7M8/GX7T7ewFGUB/xsdrcO4yXjqHnAe0yLf8epDjC + hh76bYqwwinVrmfcNcRxFVJZW2z0gGdgkOkOLaVVb9ggPV2SNAHbN4A+St/iRYR5 + awIDAQABo1MwUTAdBgNVHQ4EFgQUzMaCqVM30EZFfTeNUIJ5fNPAhaQwHwYDVR0j + BBgwFoAUzMaCqVM30EZFfTeNUIJ5fNPAhaQwDwYDVR0TAQH/BAUwAwEB/zANBgkq + hkiG9w0BAQsFAAOCAgEAVmk1rXtVkYR1Vs2Va/xrUaGXlFznhPU/Fft44kiEkkLp + mLVelWyAqvXYioqssZwuZnTjGz0DQPqzJjqwuGy4CHwPLmhCtfHplrbWo8a0ivYC + cL20KfZsG941siUh7LGBjTsq6mWBf2ytlFmg/fg93SgmqcEUAUcdps0JpZD8lgWB + ZMstfr6E3jaEus3OsvDD6hJNYZ5clJ5+ynLoWZ99A9JC0U46hmIZpRjbdSvasKpD + XrXTdpzyL/Do3znXE/yfoHv4//Rj2CpPHJLYRCIzvuf1mo1fWd53FjHvrbUvaHFz + CGuZROd4dC4Rx5nZw2ogIYvJ8m6HpIDkL3pBNSQJtIsvAYEQcotJoa5D/e9fu2Wr + +og37oCY4OXzViEBQvyxKD4cajNco1fgGKEaFROADwr3JceGI7Anq5W+xdUvAGNM + QuGeCueqNyrJ0CbQ1zEhwgpk/VYfB0u9m0bjMellRlKMdojby+FDCJtAJesx9no4 + SQXyx+aNHhj3qReysjGNwZvBk1IHL04HAT+ogNiYhTl1J/YON4MB5UN6Y2PxP6uG + KvJGPigx4fAwfR/d78o5ngwoH9m+8FUg8+qllJ8XgIbl/VXKTk3G4ceOm4eBmrel + DwWuBhELSjtXWPWhMlkiebgejDbAear53Lia2Cc43zx/KuhMHBTlKY/vY4F2YiI= + -----END CERTIFICATE----- + keyFile: | + -----BEGIN PRIVATE KEY----- + MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDn/rPsZ74oypiw + CzLlx57zplTiCi/WLSF+MmLGuTvMEQnV+OND2zFgvDIV/vFs3brkd6rLVI4NcdgS + j4YKULCMwwOl45hQPdCTEPJvUhCmM+FuQ0czmEEJSjZtdLFz1B7QB/JemNnbfigx + M9mlg58AlBhVJqn8q64wd/kC/W/KJTLJuBiVf12ZiPoPfO4WSxAqD3opZ8gdbmK0 + KYQAhKjEto6ZrYGisfwU1gt3l8M7sCJSpEkOkpuQgJ8D+xzJS36VXBJQMMP9nAPp + s+x/rGFplsPMsXEFFiwvR1+FJZwzlg2sJ91bLGZQ7vn74MfsGrxpiJwllRThJyT7 + C9V0sjb5trT2lEqZlP2dRSJYt7aJ1crEcdGSl6RIKgxSV6Hk8dh/ZaTjrTwaKxVk + Po2IeEXy5xrR7DyonOQ6Yes0KOCmJB5yHkFlIVEnLm/HZXEtm3bPHsFgTZuInyBC + OMXpUESuVZIw8YK+Vd6AExGPPwZ4n5I/sCDxWII9owIj3LeLzdUG6JoroahhGmo8 + rgpbJpPnS+VgryQ/raUQjqDzDCuE9vKXKBlSUqK6H9A+NMc0mme7M8/GX7T7ewFG + UB/xsdrcO4yXjqHnAe0yLf8epDjChh76bYqwwinVrmfcNcRxFVJZW2z0gGdgkOkO + LaVVb9ggPV2SNAHbN4A+St/iRYR5awIDAQABAoICAQCTxuixQ/wbW8IbEWcgeyHD + LkaPndGO6jyVeF73GvL+MDRFuj558NvpNLfqzvTWVf9AnQGMd5Xs9oGegRHu7Csp + 3ucp+moBYv7DT14+jtXQKOgGJpDqSqfS1RUKb/TBRXNDLGy02UScziWoAdE33zmf + UraVNwW8z1crxKA3yVw2Na++UqhGQlVLAbfXucqnJLVtNWKpkVQlezUgcfmFovsm + Iut+9MjI6/sZAqdXTLKuCKo0XjWzNKwnRecE0CYsCwzc80MvFYEiwQi1C0kwoouC + iOi8MKM/jDok+5/a3nQ7X+/ho5sbApNCJpfSXAK9YOJ3ju93+RjNuvORfp4/sW3W + OGXw6X30Ym7WS/7oYuwEILyqdyNOvKU7a+17d/W/YA60NOdA4iJI3aTfYFMD3l14 + Da+D/wkTlEN3Ye7GN21A9AsZwWWiT9G5FOxWWVv7nTPG+Ix5ewehQWt/3DxhSizR + inMBizL5xpwx9LRWHnXX277lChYmPFAAMXINl1hnX6s0EY9pSDHN0IddibJkNKBD + m1CN37rqxoXQz4zoAyJGfQVkakqe16ayqI9yuQwO6AUkZcD5DYQdz9QYOTnYrQc6 + 6haC3D0Fmqg1s4v+6gpxZA/qTri0gVl/v/NN4Mk2/qWtK33imOedgD+5LXhZdBgJ + Mqn53AErG/AT622jvSb5UQKCAQEA/DTGLh0Ct97PCm+c+PxRFyieaHNJLWENKyxp + HoWGHfp2Bvt2Vphoi7GpRCM/yta4vCZgZmeWTQ0yBg6iPVPRA6Ho5hqh9OkUYVoh + prL3JsIU20jTutYjo2aefO4qXnJfkkXxNO2FElUHDTwtWdlGJQKvlUJwTv6xO19v + bQQkhZSpri6gIpi5Nkm2SGEtDofRJ+F6ThbQibEatL6DR00dh39MYQz+tZP5olzn + kX5bHEBWB7gy+YxTGF8FdlCSQTBBtNSKsAv3Cxj4qEHm+fu09vnH6fOZKenT2nXD + 5QE/RpgQzLV1TumCjqLzqwp7bbzH+4mjsXpF3KHBZwnhMnDIRwKCAQEA63wYzjBy + no0GBBz0hOWrOwQ/AjUHfi47o3Xvl4RBjZclM171HKH7oMCnQvVKTNq8jvakCZjc + UI6i+H4R6aokiFS2xGbC2H3ZlSMFNwhb2xUs/C4Nr7JSOWZBtDy5QBspUsp26f7m + 9VNVRzCmnxWV9be/1TxHDzDhslNlL5TMejbMorWnrtNG41KWwGtwvv2gApr3894j + eJNOh0WGfsMkXUM6+4v4WcCGrdV8Cr6Nvu96ZZe2PWu2dANtAfnxqogXXCoFE6r1 + vie7hFSfJ2QR/vEbanED4pYGTtGYP1oseScx0u0hLhGLGccVBUNZlRbox4rIOELI + v9MLuiOL4YX7vQKCAQAGzMl3HtMe8AP3DRFXaT4qeK7ktA8KCS7YtibTatg14LXj + 9E25gfx3n7+nlae3qVhrwkEhIbPcuflaTnSzYJonFet4oMkzGEGzakG0A+lEA0Ga + s/j5daKaWj71sVo1F7JZ+EbLnYfT+bTp93BllsUcZFkllhf/GUDgD++qKc1uSJbW + mm044ZNE0nH2u6ACX0kVYS/yAQ14WO0WaHiTqJGeQKFnkHkhni7B4O1hb923AkkP + hjjhn5Xx90Xnbb6zwUBURtLCcmAjzXWO29AFd3Lmoc9xEF9V0PckUb6JYyI4ngr9 + 6fqSuRsLC3u0ZeD0EX322zwtodVWYIodZBfNS1srAoIBAQCjTUPGeUKDQTjS0WGg + Z8T/AErRtQSlNFqXWMn2QPlUv2RE460HVi2xpOhZPtFvyqDIY7IOFbtzAfdya7rw + V9VN1bGJMdodV+jzy31qVJmerGit2SIUnYz30TnvS80L78oQZ+dfDi4MIuYYoFxs + JgQAipS1wz9kAXoCuGKLRJ0og6gVjfPjARE/w55XgiqFyEyWgfFBZOMkUsM6e7Rx + Y9Jr+puEpeRsGV9MXafPq6WQq3It0a/HmFLG0TlfDX3RzN6mQ12R7hTM8bDQa/6S + yorQSVPB1O3kzDVDo4X5KQd+XPfoVhmUYQYdsjmZlMMi6Og0uMFwgp/Epw6S3uO6 + WbfhAoIBAQCOp4iIc87GyxWL8u6HrJaqmFlqkfou0hI+y9h6FfzsBYU6y3+gRYdF + wr2S9EUAb80kEQ1v0pt9417NOGc1pmYjKCZmDZ7qeGCGk2PR0U59+xJetXBWWhbq + 5JxcwdRYoHyrmC/LINxzzqYOQbQevbW0zcEskeKfJsOtj9WJt6U9B1YZbE8pu2QV + xjvb+YekD2R+n/umV6eiaGfDau+EWudYVTqY0mR7y9hTiFR/KnqSsy2BUjljpacS + XBQO4ig7vY8+1+L3w2xpTN95/rXAvB4BbO/DLea9ArikePoSJ+bVTj0YwrKBghep + kOvbvVANrpsunlSAcpXm1qkV+G+xPnyJ + -----END PRIVATE KEY----- diff --git a/cmd/flux/testdata/create_secret/tls/secret-tls.yaml b/cmd/flux/testdata/create_secret/tls/secret-tls.yaml index 359af2b78a..047c9221c7 100644 --- a/cmd/flux/testdata/create_secret/tls/secret-tls.yaml +++ b/cmd/flux/testdata/create_secret/tls/secret-tls.yaml @@ -5,7 +5,19 @@ metadata: name: certs namespace: my-namespace stringData: - certFile: | + ca.crt: | + -----BEGIN CERTIFICATE----- + MIIBhzCCAS2gAwIBAgIUdsAtiX3gN0uk7ddxASWYE/tdv0wwCgYIKoZIzj0EAwIw + GTEXMBUGA1UEAxMOZXhhbXBsZS5jb20gQ0EwHhcNMjAwNDE3MDgxODAwWhcNMjUw + NDE2MDgxODAwWjAZMRcwFQYDVQQDEw5leGFtcGxlLmNvbSBDQTBZMBMGByqGSM49 + AgEGCCqGSM49AwEHA0IABK7h/5D8bV93MmEdhu02JsS6ugB8s6PzRl3PV4xs3Sbr + RNkkM59+x3b0iWx/i76qPYpNLoiVUVXQmA9Y+4DbMxijUzBRMA4GA1UdDwEB/wQE + AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQGyUiU1QEZiMAqjsnIYTwZ + 4yp5wzAPBgNVHREECDAGhwR/AAABMAoGCCqGSM49BAMCA0gAMEUCIQDzdtvKdE8O + 1+WRTZ9MuSiFYcrEz7Zne7VXouDEKqKEigIgM4WlbDeuNCKbqhqj+xZV0pa3rweb + OD8EjjCMY69RMO0= + -----END CERTIFICATE----- + tls.crt: | -----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIUT84jeO/ncOrqI+FY05Fzbg8Ed7MwDQYJKoZIhvcNAQEL BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM @@ -37,7 +49,7 @@ stringData: KvJGPigx4fAwfR/d78o5ngwoH9m+8FUg8+qllJ8XgIbl/VXKTk3G4ceOm4eBmrel DwWuBhELSjtXWPWhMlkiebgejDbAear53Lia2Cc43zx/KuhMHBTlKY/vY4F2YiI= -----END CERTIFICATE----- - keyFile: | + tls.key: | -----BEGIN PRIVATE KEY----- MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDn/rPsZ74oypiw CzLlx57zplTiCi/WLSF+MmLGuTvMEQnV+OND2zFgvDIV/vFs3brkd6rLVI4NcdgS @@ -90,3 +102,4 @@ stringData: XBQO4ig7vY8+1+L3w2xpTN95/rXAvB4BbO/DLea9ArikePoSJ+bVTj0YwrKBghep kOvbvVANrpsunlSAcpXm1qkV+G+xPnyJ -----END PRIVATE KEY----- +type: kubernetes.io/tls diff --git a/cmd/flux/testdata/create_secret/tls/test-ca.pem b/cmd/flux/testdata/create_secret/tls/test-ca.pem new file mode 100644 index 0000000000..080bd24e6b --- /dev/null +++ b/cmd/flux/testdata/create_secret/tls/test-ca.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBhzCCAS2gAwIBAgIUdsAtiX3gN0uk7ddxASWYE/tdv0wwCgYIKoZIzj0EAwIw +GTEXMBUGA1UEAxMOZXhhbXBsZS5jb20gQ0EwHhcNMjAwNDE3MDgxODAwWhcNMjUw +NDE2MDgxODAwWjAZMRcwFQYDVQQDEw5leGFtcGxlLmNvbSBDQTBZMBMGByqGSM49 +AgEGCCqGSM49AwEHA0IABK7h/5D8bV93MmEdhu02JsS6ugB8s6PzRl3PV4xs3Sbr +RNkkM59+x3b0iWx/i76qPYpNLoiVUVXQmA9Y+4DbMxijUzBRMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQGyUiU1QEZiMAqjsnIYTwZ +4yp5wzAPBgNVHREECDAGhwR/AAABMAoGCCqGSM49BAMCA0gAMEUCIQDzdtvKdE8O +1+WRTZ9MuSiFYcrEz7Zne7VXouDEKqKEigIgM4WlbDeuNCKbqhqj+xZV0pa3rweb +OD8EjjCMY69RMO0= +-----END CERTIFICATE----- diff --git a/cmd/flux/testdata/create_source_chart/basic.yaml b/cmd/flux/testdata/create_source_chart/basic.yaml new file mode 100644 index 0000000000..fc7dc51da9 --- /dev/null +++ b/cmd/flux/testdata/create_source_chart/basic.yaml @@ -0,0 +1,14 @@ +✚ generating HelmChart source +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmChart +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + chart: podinfo + interval: 0s + reconcileStrategy: ChartVersion + sourceRef: + kind: HelmRepository + name: podinfo diff --git a/cmd/flux/testdata/create_source_chart/setup-source.yaml b/cmd/flux/testdata/create_source_chart/setup-source.yaml new file mode 100644 index 0000000000..18787c728d --- /dev/null +++ b/cmd/flux/testdata/create_source_chart/setup-source.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .fluxns }} +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + interval: 1m0s + provider: generic + type: oci + url: oci://ghcr.io/stefanprodan/charts diff --git a/cmd/flux/testdata/create_source_chart/verify_basic.yaml b/cmd/flux/testdata/create_source_chart/verify_basic.yaml new file mode 100644 index 0000000000..46ce5dedd8 --- /dev/null +++ b/cmd/flux/testdata/create_source_chart/verify_basic.yaml @@ -0,0 +1,16 @@ +✚ generating HelmChart source +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmChart +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + chart: podinfo + interval: 0s + reconcileStrategy: ChartVersion + sourceRef: + kind: HelmRepository + name: podinfo + verify: + provider: cosign diff --git a/cmd/flux/testdata/create_source_chart/verify_complete.yaml b/cmd/flux/testdata/create_source_chart/verify_complete.yaml new file mode 100644 index 0000000000..73b32b024b --- /dev/null +++ b/cmd/flux/testdata/create_source_chart/verify_complete.yaml @@ -0,0 +1,19 @@ +✚ generating HelmChart source +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmChart +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + chart: podinfo + interval: 0s + reconcileStrategy: ChartVersion + sourceRef: + kind: HelmRepository + name: podinfo + verify: + matchOIDCIdentity: + - issuer: foo + subject: bar + provider: cosign diff --git a/cmd/flux/testdata/create_source_helm/https.golden b/cmd/flux/testdata/create_source_helm/https.golden index 8723524547..02da00a5a3 100644 --- a/cmd/flux/testdata/create_source_helm/https.golden +++ b/cmd/flux/testdata/create_source_helm/https.golden @@ -1,5 +1,5 @@ --- -apiVersion: source.toolkit.fluxcd.io/v1beta2 +apiVersion: source.toolkit.fluxcd.io/v1 kind: HelmRepository metadata: name: podinfo diff --git a/cmd/flux/testdata/create_source_helm/oci-with-secret.golden b/cmd/flux/testdata/create_source_helm/oci-with-secret.golden index 760a265c63..d01f6f7245 100644 --- a/cmd/flux/testdata/create_source_helm/oci-with-secret.golden +++ b/cmd/flux/testdata/create_source_helm/oci-with-secret.golden @@ -1,5 +1,5 @@ --- -apiVersion: source.toolkit.fluxcd.io/v1beta2 +apiVersion: source.toolkit.fluxcd.io/v1 kind: HelmRepository metadata: name: podinfo diff --git a/cmd/flux/testdata/create_source_helm/oci.golden b/cmd/flux/testdata/create_source_helm/oci.golden index f6049e99f6..92f095587b 100644 --- a/cmd/flux/testdata/create_source_helm/oci.golden +++ b/cmd/flux/testdata/create_source_helm/oci.golden @@ -1,5 +1,5 @@ --- -apiVersion: source.toolkit.fluxcd.io/v1beta2 +apiVersion: source.toolkit.fluxcd.io/v1 kind: HelmRepository metadata: name: podinfo diff --git a/cmd/flux/testdata/diff-kustomization/diff-with-drifted-secret.golden b/cmd/flux/testdata/diff-kustomization/diff-with-drifted-secret.golden index 41a65058fd..bfce21b535 100644 --- a/cmd/flux/testdata/diff-kustomization/diff-with-drifted-secret.golden +++ b/cmd/flux/testdata/diff-kustomization/diff-with-drifted-secret.golden @@ -8,6 +8,6 @@ data.password ± value change - - ****** - + ***** + - *** (before) + + *** (after) diff --git a/cmd/flux/testdata/diff-kustomization/flux-kustomization-multiobj.yaml b/cmd/flux/testdata/diff-kustomization/flux-kustomization-multiobj.yaml new file mode 100644 index 0000000000..53b03336ff --- /dev/null +++ b/cmd/flux/testdata/diff-kustomization/flux-kustomization-multiobj.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: podinfo + +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: podinfo +spec: + interval: 5m0s + path: ./kustomize + force: true + prune: true + sourceRef: + kind: GitRepository + name: podinfo + targetNamespace: default diff --git a/cmd/flux/testdata/envsubst/file.gold b/cmd/flux/testdata/envsubst/file.gold new file mode 100644 index 0000000000..58ac924e1a --- /dev/null +++ b/cmd/flux/testdata/envsubst/file.gold @@ -0,0 +1,10 @@ +apiVersion: source.toolkit.fluxcd.io/v1 +kind: GitRepository +metadata: + name: test + namespace: flux-system +spec: + ref: + branch: main + interval: 5m + url: ssh://git@github.com/example/test diff --git a/cmd/flux/testdata/envsubst/file.yaml b/cmd/flux/testdata/envsubst/file.yaml new file mode 100644 index 0000000000..e54aa24f0e --- /dev/null +++ b/cmd/flux/testdata/envsubst/file.yaml @@ -0,0 +1,10 @@ +apiVersion: source.toolkit.fluxcd.io/v1 +kind: GitRepository +metadata: + name: ${REPO_NAME} + namespace: ${REPO_NAMESPACE:=flux-system} +spec: + ref: + branch: main + interval: 5m + url: ssh://git@github.com/example/${REPO_NAME} diff --git a/cmd/flux/testdata/export/alert.yaml b/cmd/flux/testdata/export/alert.yaml index 4619affb4d..e5ffba02c1 100644 --- a/cmd/flux/testdata/export/alert.yaml +++ b/cmd/flux/testdata/export/alert.yaml @@ -1,5 +1,5 @@ --- -apiVersion: notification.toolkit.fluxcd.io/v1beta2 +apiVersion: notification.toolkit.fluxcd.io/v1beta3 kind: Alert metadata: name: flux-system diff --git a/cmd/flux/testdata/export/helm-chart.yaml b/cmd/flux/testdata/export/helm-chart.yaml new file mode 100644 index 0000000000..7300917c52 --- /dev/null +++ b/cmd/flux/testdata/export/helm-chart.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmChart +metadata: + name: flux-system + namespace: {{ .fluxns }} +spec: + chart: podinfo + interval: 1m0s + reconcileStrategy: ChartVersion + sourceRef: + kind: HelmRepository + name: podinfo + version: '*' diff --git a/cmd/flux/testdata/export/helm-release.yaml b/cmd/flux/testdata/export/helm-release.yaml index b6c3b12630..cabd8db5cc 100644 --- a/cmd/flux/testdata/export/helm-release.yaml +++ b/cmd/flux/testdata/export/helm-release.yaml @@ -1,5 +1,5 @@ --- -apiVersion: helm.toolkit.fluxcd.io/v2beta1 +apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: flux-system diff --git a/cmd/flux/testdata/export/helm-repo.yaml b/cmd/flux/testdata/export/helm-repo.yaml index c5366e0baf..685b9079e0 100644 --- a/cmd/flux/testdata/export/helm-repo.yaml +++ b/cmd/flux/testdata/export/helm-repo.yaml @@ -1,5 +1,5 @@ --- -apiVersion: source.toolkit.fluxcd.io/v1beta2 +apiVersion: source.toolkit.fluxcd.io/v1 kind: HelmRepository metadata: name: flux-system diff --git a/cmd/flux/testdata/export/image-update.yaml b/cmd/flux/testdata/export/image-update.yaml index e2fcb3c1fa..c78757aa58 100644 --- a/cmd/flux/testdata/export/image-update.yaml +++ b/cmd/flux/testdata/export/image-update.yaml @@ -1,5 +1,5 @@ --- -apiVersion: image.toolkit.fluxcd.io/v1beta1 +apiVersion: image.toolkit.fluxcd.io/v1beta2 kind: ImageUpdateAutomation metadata: name: flux-system diff --git a/cmd/flux/testdata/export/objects.yaml b/cmd/flux/testdata/export/objects.yaml index aa1b6f196a..a512e62c8f 100644 --- a/cmd/flux/testdata/export/objects.yaml +++ b/cmd/flux/testdata/export/objects.yaml @@ -4,7 +4,7 @@ kind: Namespace metadata: name: {{ .fluxns }} --- -apiVersion: notification.toolkit.fluxcd.io/v1beta2 +apiVersion: notification.toolkit.fluxcd.io/v1beta3 kind: Provider metadata: name: slack @@ -14,7 +14,7 @@ spec: channel: 'A channel with spacess' address: https://hooks.slack.com/services/mock --- -apiVersion: notification.toolkit.fluxcd.io/v1beta2 +apiVersion: notification.toolkit.fluxcd.io/v1beta3 kind: Alert metadata: name: flux-system @@ -51,7 +51,7 @@ spec: semver: range: 5.0.x --- -apiVersion: image.toolkit.fluxcd.io/v1beta1 +apiVersion: image.toolkit.fluxcd.io/v1beta2 kind: ImageUpdateAutomation metadata: name: flux-system @@ -114,7 +114,7 @@ spec: name: flux-system namespace: flux-system --- -apiVersion: source.toolkit.fluxcd.io/v1beta2 +apiVersion: source.toolkit.fluxcd.io/v1 kind: HelmRepository metadata: name: flux-system @@ -124,7 +124,21 @@ spec: timeout: 1m0s url: https://stefanprodan.github.io/podinfo --- -apiVersion: helm.toolkit.fluxcd.io/v2beta1 +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmChart +metadata: + name: flux-system + namespace: {{ .fluxns }} +spec: + chart: podinfo + interval: 1m0s + reconcileStrategy: ChartVersion + sourceRef: + kind: HelmRepository + name: podinfo + version: '*' +--- +apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: flux-system diff --git a/cmd/flux/testdata/export/provider.yaml b/cmd/flux/testdata/export/provider.yaml index 671044537c..c3ca08e3cf 100644 --- a/cmd/flux/testdata/export/provider.yaml +++ b/cmd/flux/testdata/export/provider.yaml @@ -1,5 +1,5 @@ --- -apiVersion: notification.toolkit.fluxcd.io/v1beta2 +apiVersion: notification.toolkit.fluxcd.io/v1beta3 kind: Provider metadata: name: slack diff --git a/cmd/flux/testdata/helmrelease/get_helmrelease_from_git.golden b/cmd/flux/testdata/helmrelease/get_helmrelease_from_git.golden index b384ecb8f8..8bd236dd84 100644 --- a/cmd/flux/testdata/helmrelease/get_helmrelease_from_git.golden +++ b/cmd/flux/testdata/helmrelease/get_helmrelease_from_git.golden @@ -1,2 +1,2 @@ -NAME REVISION SUSPENDED READY MESSAGE -thrfg 6.3.5 False True Release reconciliation succeeded +NAME REVISION SUSPENDED READY MESSAGE +thrfg 6.3.5 False True Helm install succeeded for release thrfg-1/thrfg.v1 with chart podinfo@6.3.5 diff --git a/cmd/flux/testdata/oci/export_with_complete_verification.golden b/cmd/flux/testdata/oci/export_with_complete_verification.golden new file mode 100644 index 0000000000..8b862a83dd --- /dev/null +++ b/cmd/flux/testdata/oci/export_with_complete_verification.golden @@ -0,0 +1,16 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 0s + ref: + tag: 6.3.5 + url: oci://ghcr.io/stefanprodan/manifests/podinfo + verify: + matchOIDCIdentity: + - issuer: github + subject: stefanprodan + provider: cosign diff --git a/cmd/flux/testdata/oci/export_with_issuer.golden b/cmd/flux/testdata/oci/export_with_issuer.golden new file mode 100644 index 0000000000..3abaf36a1b --- /dev/null +++ b/cmd/flux/testdata/oci/export_with_issuer.golden @@ -0,0 +1,16 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 0s + ref: + tag: 6.3.5 + url: oci://ghcr.io/stefanprodan/manifests/podinfo + verify: + matchOIDCIdentity: + - issuer: github + subject: "" + provider: cosign diff --git a/cmd/flux/testdata/oci/export_with_subject.golden b/cmd/flux/testdata/oci/export_with_subject.golden new file mode 100644 index 0000000000..93eca53b88 --- /dev/null +++ b/cmd/flux/testdata/oci/export_with_subject.golden @@ -0,0 +1,16 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 0s + ref: + tag: 6.3.5 + url: oci://ghcr.io/stefanprodan/manifests/podinfo + verify: + matchOIDCIdentity: + - issuer: "" + subject: stefanprodan + provider: cosign diff --git a/cmd/flux/testdata/oci/export_with_verify_secret.golden b/cmd/flux/testdata/oci/export_with_verify_secret.golden new file mode 100644 index 0000000000..d7e94aadde --- /dev/null +++ b/cmd/flux/testdata/oci/export_with_verify_secret.golden @@ -0,0 +1,15 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 10m0s + ref: + tag: 6.3.5 + url: oci://ghcr.io/stefanprodan/manifests/podinfo + verify: + provider: cosign + secretRef: + name: cosign-pub diff --git a/cmd/flux/testdata/trace/deployment.yaml b/cmd/flux/testdata/trace/deployment.yaml index 01580dfeb3..4e28b8e911 100644 --- a/cmd/flux/testdata/trace/deployment.yaml +++ b/cmd/flux/testdata/trace/deployment.yaml @@ -34,7 +34,7 @@ spec: command: [ "echo hello world" ] image: busybox --- -apiVersion: helm.toolkit.fluxcd.io/v2beta1 +apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: labels: @@ -59,11 +59,9 @@ status: status: "True" type: Ready helmChart: {{ .fluxns }}/podinfo-podinfo - lastAppliedRevision: 6.3.5 lastAttemptedRevision: 6.3.5 - lastAttemptedValuesChecksum: c31db75d05b7515eba2eef47bd71038c74b2e531 --- -apiVersion: source.toolkit.fluxcd.io/v1beta2 +apiVersion: source.toolkit.fluxcd.io/v1 kind: HelmChart metadata: name: podinfo-podinfo @@ -90,7 +88,7 @@ status: type: Ready --- -apiVersion: source.toolkit.fluxcd.io/v1beta2 +apiVersion: source.toolkit.fluxcd.io/v1 kind: HelmRepository metadata: labels: @@ -126,7 +124,6 @@ spec: sourceRef: kind: GitRepository name: flux-system - validation: client interval: 5m prune: true status: diff --git a/cmd/flux/testdata/trace/helmrelease-oci.yaml b/cmd/flux/testdata/trace/helmrelease-oci.yaml index da9d1d47af..d4ac8f7267 100644 --- a/cmd/flux/testdata/trace/helmrelease-oci.yaml +++ b/cmd/flux/testdata/trace/helmrelease-oci.yaml @@ -9,7 +9,7 @@ kind: Namespace metadata: name: {{ .ns }} --- -apiVersion: helm.toolkit.fluxcd.io/v2beta1 +apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: labels: @@ -48,7 +48,6 @@ spec: sourceRef: kind: OCIRepository name: flux-system - validation: client interval: 5m prune: false status: diff --git a/cmd/flux/testdata/trace/helmrelease.yaml b/cmd/flux/testdata/trace/helmrelease.yaml index 9c626f4aff..d82a8c038b 100644 --- a/cmd/flux/testdata/trace/helmrelease.yaml +++ b/cmd/flux/testdata/trace/helmrelease.yaml @@ -9,7 +9,7 @@ kind: Namespace metadata: name: {{ .ns }} --- -apiVersion: helm.toolkit.fluxcd.io/v2beta1 +apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: labels: @@ -48,7 +48,6 @@ spec: sourceRef: kind: GitRepository name: flux-system - validation: client interval: 5m prune: false status: diff --git a/cmd/flux/trace.go b/cmd/flux/trace.go index d85f10eaa0..a62e9a529a 100644 --- a/cmd/flux/trace.go +++ b/cmd/flux/trace.go @@ -33,13 +33,14 @@ import ( "k8s.io/cli-runtime/pkg/resource" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/fluxcd/flux2/v2/internal/utils" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" fluxmeta "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/oci" sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" + + "github.com/fluxcd/flux2/v2/internal/utils" ) var traceCmd = &cobra.Command{ @@ -63,7 +64,7 @@ You can also trace multiple objects with different resource kinds using github.com/go-git/go-git/v5 v5.7.1-0.20230702134234-dd4e2b7f4b01 +// Fix CVE-2022-28948. +replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1 require ( github.com/Masterminds/semver/v3 v3.2.1 - github.com/ProtonMail/go-crypto v0.0.0-20230710112148-e01326fd72eb - github.com/cyphar/filepath-securejoin v0.2.3 - github.com/distribution/distribution/v3 v3.0.0-20230711080520-40ef2353fa4f - github.com/fluxcd/go-git-providers v0.18.0 - github.com/fluxcd/helm-controller/api v0.35.0 - github.com/fluxcd/image-automation-controller/api v0.35.0 - github.com/fluxcd/image-reflector-controller/api v0.29.1 - github.com/fluxcd/kustomize-controller/api v1.0.1 - github.com/fluxcd/notification-controller/api v1.0.0 - github.com/fluxcd/pkg/apis/event v0.5.1 - github.com/fluxcd/pkg/apis/meta v1.1.1 - github.com/fluxcd/pkg/git v0.12.3 - github.com/fluxcd/pkg/git/gogit v0.12.1 - github.com/fluxcd/pkg/kustomize v1.3.4 - github.com/fluxcd/pkg/oci v0.29.0 - github.com/fluxcd/pkg/runtime v0.40.0 - github.com/fluxcd/pkg/sourceignore v0.3.4 - github.com/fluxcd/pkg/ssa v0.28.2 - github.com/fluxcd/pkg/ssh v0.8.0 - github.com/fluxcd/pkg/tar v0.2.0 - github.com/fluxcd/pkg/version v0.2.2 - github.com/fluxcd/source-controller/api v1.0.1 - github.com/go-git/go-git/v5 v5.7.0 - github.com/go-logr/logr v1.2.4 + github.com/ProtonMail/go-crypto v1.0.0 + github.com/cyphar/filepath-securejoin v0.2.5 + github.com/distribution/distribution/v3 v3.0.0-alpha.1 + github.com/fluxcd/cli-utils v0.36.0-flux.7 + github.com/fluxcd/go-git-providers v0.20.1 + github.com/fluxcd/helm-controller/api v1.0.1 + github.com/fluxcd/image-automation-controller/api v0.38.0 + github.com/fluxcd/image-reflector-controller/api v0.32.0 + github.com/fluxcd/kustomize-controller/api v1.3.0 + github.com/fluxcd/notification-controller/api v1.3.0 + github.com/fluxcd/pkg/apis/event v0.9.0 + github.com/fluxcd/pkg/apis/meta v1.5.0 + github.com/fluxcd/pkg/envsubst v1.1.0 + github.com/fluxcd/pkg/git v0.19.0 + github.com/fluxcd/pkg/git/gogit v0.19.0 + github.com/fluxcd/pkg/kustomize v1.11.0 + github.com/fluxcd/pkg/oci v0.37.1 + github.com/fluxcd/pkg/runtime v0.47.1 + github.com/fluxcd/pkg/sourceignore v0.7.0 + github.com/fluxcd/pkg/ssa v0.39.1 + github.com/fluxcd/pkg/ssh v0.13.0 + github.com/fluxcd/pkg/tar v0.7.0 + github.com/fluxcd/pkg/version v0.4.0 + github.com/fluxcd/source-controller/api v1.3.0 + github.com/go-git/go-git/v5 v5.12.0 + github.com/go-logr/logr v1.4.1 github.com/gonvenience/bunt v1.3.5 github.com/gonvenience/ytbx v1.4.4 - github.com/google/go-cmp v0.5.9 - github.com/google/go-containerregistry v0.15.2 - github.com/homeport/dyff v1.5.8 + github.com/google/go-cmp v0.6.0 + github.com/google/go-containerregistry v0.19.1 + github.com/hashicorp/go-cleanhttp v0.5.2 + github.com/homeport/dyff v1.7.1 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/manifoldco/promptui v0.9.0 github.com/mattn/go-shellwords v1.0.12 + github.com/notaryproject/notation-go v1.1.0 github.com/olekukonko/tablewriter v0.0.5 - github.com/onsi/gomega v1.27.8 + github.com/onsi/gomega v1.33.1 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 - github.com/spf13/cobra v1.7.0 + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/theckman/yacspin v0.13.12 - golang.org/x/crypto v0.11.0 - golang.org/x/term v0.10.0 - k8s.io/api v0.27.3 - k8s.io/apiextensions-apiserver v0.27.3 - k8s.io/apimachinery v0.27.3 - k8s.io/cli-runtime v0.27.3 - k8s.io/client-go v0.27.3 - k8s.io/kubectl v0.27.3 - sigs.k8s.io/cli-utils v0.34.0 - sigs.k8s.io/controller-runtime v0.15.0 - sigs.k8s.io/kustomize/api v0.13.4 - sigs.k8s.io/kustomize/kyaml v0.14.2 - sigs.k8s.io/yaml v1.3.0 + golang.org/x/crypto v0.22.0 + golang.org/x/term v0.19.0 + golang.org/x/text v0.14.0 + k8s.io/api v0.30.0 + k8s.io/apiextensions-apiserver v0.30.0 + k8s.io/apimachinery v0.30.0 + k8s.io/cli-runtime v0.30.0 + k8s.io/client-go v0.30.0 + k8s.io/kubectl v0.30.0 + sigs.k8s.io/controller-runtime v0.18.1 + sigs.k8s.io/kustomize/api v0.17.1 + sigs.k8s.io/kustomize/kyaml v0.17.0 + sigs.k8s.io/yaml v1.4.0 ) -// Fix CVE-2022-28948 -replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1 - require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect + code.gitea.io/sdk/gitea v0.17.1 // indirect + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect - github.com/acomagu/bufpipe v1.0.4 // indirect - github.com/aws/aws-sdk-go-v2 v1.18.1 // indirect - github.com/aws/aws-sdk-go-v2/config v1.18.27 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.26 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 // indirect - github.com/aws/aws-sdk-go-v2/service/ecr v1.18.13 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 // indirect - github.com/aws/smithy-go v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.11 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.11 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect + github.com/aws/smithy-go v1.20.2 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect - github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd // indirect - github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect - github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect - github.com/cloudflare/circl v1.3.3 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v23.0.5+incompatible // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/docker/cli v24.0.9+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v23.0.5+incompatible // indirect + github.com/docker/docker v24.0.9+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect - github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect - github.com/drone/envsubst v1.0.3 // indirect - github.com/emicklei/go-restful/v3 v3.10.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/evanphx/json-patch v5.7.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect - github.com/fluxcd/pkg/apis/kustomize v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-errors/errors v1.4.2 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fluxcd/pkg/apis/acl v0.3.0 // indirect + github.com/fluxcd/pkg/apis/kustomize v1.5.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect + github.com/go-errors/errors v1.5.1 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.4.1 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.1 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-ldap/ldap/v3 v3.4.6 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/gomodule/redigo v1.8.2 // indirect - github.com/gonvenience/neat v1.3.12 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/gonvenience/neat v1.3.13 // indirect github.com/gonvenience/term v1.0.2 // indirect github.com/gonvenience/text v1.0.7 // indirect - github.com/gonvenience/wrap v1.1.2 // indirect + github.com/gonvenience/wrap v1.2.0 // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/gnostic v0.6.9 // indirect - github.com/google/go-github/v52 v52.0.0 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-github/v61 v61.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect - github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.4 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/imdario/mergo v0.3.15 // indirect + github.com/hashicorp/go-retryablehttp v0.7.5 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.16.5 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -162,61 +172,82 @@ require ( github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/hashstructure v1.1.0 // indirect github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/notaryproject/notation-core-go v1.0.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc3 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.53.0 // indirect + github.com/prometheus/procfs v0.14.0 // indirect + github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect + github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect + github.com/redis/go-redis/v9 v9.1.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sergi/go-diff v1.3.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.1.1 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/texttheater/golang-levenshtein v1.0.1 // indirect github.com/vbatts/tar-split v0.11.3 // indirect github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect - github.com/xanzy/go-gitlab v0.83.0 // indirect + github.com/xanzy/go-gitlab v0.101.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/xlab/treeprint v1.1.0 // indirect - github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect - github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect - github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect - go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 // indirect - golang.org/x/mod v0.11.0 // indirect - golang.org/x/net v0.11.0 // indirect - golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.10.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.30.0 // indirect + github.com/xlab/treeprint v1.2.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.46.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect + go.opentelemetry.io/otel v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.44.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 // indirect + go.opentelemetry.io/otel/metric v1.21.0 // indirect + go.opentelemetry.io/otel/sdk v1.21.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect + go.opentelemetry.io/otel/trace v1.21.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.starlark.net v0.0.0-20231121155337-90ade8b19d09 // indirect + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/oauth2 v0.19.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.20.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.59.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.27.3 // indirect - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect + k8s.io/component-base v0.30.0 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 // indirect + k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/go.sum b/go.sum index e7dd3b72bb..a335067734 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,21 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 h1:SEy2xmstIphdPwNBUi7uhvjyjhVKISfwjfOJmuy7kg4= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= -github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8= +code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 h1:FDif4R1+UUR+00q6wquyX90K7A8dN+R5E8GEadoP7sU= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -22,282 +26,286 @@ github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYr github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20230710112148-e01326fd72eb h1:RU+Ff2vE68zFQSoBqlb/LChFztEWWJ9EZ8LU4gA3ubU= -github.com/ProtonMail/go-crypto v0.0.0-20230710112148-e01326fd72eb/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= -github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/aws/aws-sdk-go-v2 v1.18.1 h1:+tefE750oAb7ZQGzla6bLkOwfcQCEtC5y2RqoqCeqKo= -github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.27 h1:Az9uLwmssTE6OGTpsFqOnaGpLnKDqNYOJzWuC6UAYzA= -github.com/aws/aws-sdk-go-v2/config v1.18.27/go.mod h1:0My+YgmkGxeqjXZb5BYme5pc4drjTnM+x1GJ3zv42Nw= -github.com/aws/aws-sdk-go-v2/credentials v1.13.26 h1:qmU+yhKmOCyujmuPY7tf5MxR/RKyZrOPO3V4DobiTUk= -github.com/aws/aws-sdk-go-v2/credentials v1.13.26/go.mod h1:GoXt2YC8jHUBbA4jr+W3JiemnIbkXOfxSXcisUsZ3os= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 h1:LxK/bitrAr4lnh9LnIS6i7zWbCOdMsfzKFBI6LUCS0I= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4/go.mod h1:E1hLXN/BL2e6YizK1zFlYd8vsfi2GTjbjBazinMmeaM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 h1:A5UqQEmPaCFpedKouS4v+dHCTUo2sKqhoKO9U5kxyWo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34/go.mod h1:wZpTEecJe0Btj3IYnDx/VlUzor9wm3fJHyvLpQF0VwY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 h1:srIVS45eQuewqz6fKKu6ZGXaq6FuFg5NzgQBAM6g8Y4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28/go.mod h1:7VRpKQQedkfIEXb4k52I7swUnZP0wohVajJMRn3vsUw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 h1:LWA+3kDM8ly001vJ1X1waCuLJdtTl48gwkPKWy9sosI= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35/go.mod h1:0Eg1YjxE0Bhn56lx+SHJwCzhW+2JGtizsrx+lCqrfm0= -github.com/aws/aws-sdk-go-v2/service/ecr v1.18.13 h1:hF7MUVNjubetjggZDtn3AmqCJzD7EUi//tSdxMYPm7U= -github.com/aws/aws-sdk-go-v2/service/ecr v1.18.13/go.mod h1:XwEFO35g0uN/SftK0asWxh8Rk6DOx37R83TmWe2tzEE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 h1:bkRyG4a929RCnpVSTvLM2j/T4ls015ZhhYApbmYs15s= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28/go.mod h1:jj7znCIg05jXlaGBlFMGP8+7UN3VtCkRBG2spnmRQkU= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 h1:nneMBM2p79PGWBQovYO/6Xnc2ryRMw3InnDJq1FHkSY= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.12/go.mod h1:HuCOxYsF21eKrerARYO6HapNeh9GBNq7fius2AcwodY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 h1:2qTR7IFk7/0IN/adSFhYu9Xthr0zVFTgBrmPldILn80= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12/go.mod h1:E4VrHCPzmVB/KFXtqBGKb3c8zpbNBgKe3fisDNLAW5w= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 h1:XFJ2Z6sNUUcAz9poj+245DMkrHE4h2j5I9/xD50RHfE= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.2/go.mod h1:dp0yLPsLBOi++WTxzCjA/oZqi6NPIhoR+uF7GeMU9eg= -github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= -github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA= +github.com/aws/aws-sdk-go-v2/config v1.27.11/go.mod h1:SMsV78RIOYdve1vf36z8LmnszlRWkwMQtomCAI0/mIE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 h1:Qr9W21mzWT3RhfYn9iAux7CeRIdbnTAqmiOlASqQgZI= +github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4/go.mod h1:if7ybzzjOmDB8pat9FE35AHTY6ZxlYSy3YviSmFZv8c= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 h1:vN8hEbpRnL7+Hopy9dzmRle1xmDc7o8tmY0klsr175w= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.5/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= +github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= +github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= +github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/distribution/distribution/v3 v3.0.0-20230711080520-40ef2353fa4f h1:Df8k7piYnfycy7zSBLkN2oQaoIz1vw+msSgajUA+yKE= -github.com/distribution/distribution/v3 v3.0.0-20230711080520-40ef2353fa4f/go.mod h1:+fqBJ4vPYo4Uu1ZE4d+bUtTLRXfdSL3NvCZIZ9GHv58= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/distribution/distribution/v3 v3.0.0-alpha.1 h1:jn7I1gvjOvmLztH1+1cLiUFud7aeJCIQcgzugtwjyJo= +github.com/distribution/distribution/v3 v3.0.0-alpha.1/go.mod h1:LCp4JZp1ZalYg0W/TN05jarCQu+h4w7xc7ZfQF4Y/cY= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuDEvFVVDafE= -github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/cli v24.0.9+incompatible h1:OxbimnP/z+qVjDLpq9wbeFU3Nc30XhSe+LkwYQisD50= +github.com/docker/cli v24.0.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v23.0.5+incompatible h1:DaxtlTJjFSnLOXVNUBU1+6kXGz2lpDoEAH6QoxaSg8k= -github.com/docker/docker v23.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= -github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= -github.com/emicklei/go-restful/v3 v3.10.0 h1:X4gma4HM7hFm6WMeAsTfqA0GOfdNoCzBIkHGoRLGXuM= -github.com/emicklei/go-restful/v3 v3.10.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M= +github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= +github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fluxcd/cli-utils v0.36.0-flux.7 h1:81zEo/LNmIRWMgtsZy/8L13TMUZHmmJib4gHRvKwVE8= +github.com/fluxcd/cli-utils v0.36.0-flux.7/go.mod h1:TcfLhvBjtQnqxYMsHQUAEB2c5WJRVuibtas2Izz5ZTs= github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= -github.com/fluxcd/go-git-providers v0.18.0 h1:4Whs6GzQdx3d7UTeIahRmzUrTZKhucySC53pwdXFV7Y= -github.com/fluxcd/go-git-providers v0.18.0/go.mod h1:++kUR1LegtRWU30bdPb9FuRUH/6+DgoHSbwOkZ5NiKo= -github.com/fluxcd/helm-controller/api v0.35.0 h1:UyhKXPni5z69DzPW7GtECGGdUwKsB+OTI0A/wc7HmFY= -github.com/fluxcd/helm-controller/api v0.35.0/go.mod h1:CdHMtr5wM0xgDt/PS147H7QQS+zDxAFgDW3ZN8MnUlU= -github.com/fluxcd/image-automation-controller/api v0.35.0 h1:B7yJdAbnx0hwI6ffVu0YAyc3d3sqjXrv6ySK++g3t28= -github.com/fluxcd/image-automation-controller/api v0.35.0/go.mod h1:67HbnrXDy550zJKZZbzAZ0yfzL0Ge1O18yn/shcgy0Y= -github.com/fluxcd/image-reflector-controller/api v0.29.1 h1:mnFaO60b8WyCxpc72utmAJ7J1ZtZjGGThH9NdxVeEyo= -github.com/fluxcd/image-reflector-controller/api v0.29.1/go.mod h1:aYwq15SpkAD/urQme2iEsNouTscfR1Qo39KBlJ+0EW8= -github.com/fluxcd/kustomize-controller/api v1.0.1 h1:zz9zx4Mc7rw9gqdgdhMWX1uDM2uR1x7WBUujKs4mdx8= -github.com/fluxcd/kustomize-controller/api v1.0.1/go.mod h1:rYUovoofr3bVPgQowWj/CSGw73qoH0tOCopJ3oNh7lM= -github.com/fluxcd/notification-controller/api v1.0.0 h1:WiXS116pwhFp7Qe/Fc1GmKCfLBRnZu+rYCeIn07cO+4= -github.com/fluxcd/notification-controller/api v1.0.0/go.mod h1:uamWZwmhSHE7xdJkgRhH4Eo5/SKKILFI4o+CoQjlczc= -github.com/fluxcd/pkg/apis/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q= -github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8= -github.com/fluxcd/pkg/apis/event v0.5.1 h1:UrEmKwTK/lt42gMZunl8BQBMzjf8PSqGbWDs/GB839c= -github.com/fluxcd/pkg/apis/event v0.5.1/go.mod h1:GzBAzS8bq7751wvNkaSnr3kuwFVuWTPL20D77UgSNJQ= -github.com/fluxcd/pkg/apis/kustomize v1.1.1 h1:MSGn4z0R9PptmoPFHnx2nEZ8Jtl1sKfw0cuDQY2HYwM= -github.com/fluxcd/pkg/apis/kustomize v1.1.1/go.mod h1:0pCu0ecIY+ZM0iE/hOHYwCMZ3b0SpBrjJ1SH3FFyYdE= -github.com/fluxcd/pkg/apis/meta v1.1.1 h1:sLAKLbEu7rRzJ+Mytffu3NcpfdbOBTa6hcpOQzFWm+M= -github.com/fluxcd/pkg/apis/meta v1.1.1/go.mod h1:soCfzjFWbm1mqybDcOywWKTCEYlH3skpoNGTboVk234= -github.com/fluxcd/pkg/git v0.12.3 h1:1KmRYTdcBKDUutg6NIT4x0BCCMT72PjjXs3AnHjybHY= -github.com/fluxcd/pkg/git v0.12.3/go.mod h1:ID2sry5OqYbgJxvANc7s6V/YwafnQd7e1AGoDvwztAU= -github.com/fluxcd/pkg/git/gogit v0.12.1 h1:06jzHOTntYN5xCSQvyFXtLXdqoP8crLh7VYgtXS9+wo= -github.com/fluxcd/pkg/git/gogit v0.12.1/go.mod h1:Z4Ysp8VifKTvWpjJMKncJsgb2iBqHuIeK80VGjlU41Y= -github.com/fluxcd/pkg/gittestserver v0.8.4 h1:rA/QUZnfH77ZZG+5xfMqjgEHJdzeeE6Nn1o8cops/bU= -github.com/fluxcd/pkg/kustomize v1.3.4 h1:+XA4umfhkFqrNYQp3ZmKSJy4i3LWjbgoi6H4PllkkkA= -github.com/fluxcd/pkg/kustomize v1.3.4/go.mod h1:nsbsNGe6nQY6DGYYPowFGQlhxFRcaRFvbwqhCbwAQuE= -github.com/fluxcd/pkg/oci v0.29.0 h1:LACJNooDB4ZWVqRkB+V5MSPE04327DBbI/8BdWFzP8Y= -github.com/fluxcd/pkg/oci v0.29.0/go.mod h1:x91ESIQjUimZSElpJTVRx3yPFBmimvulni9iBsCwXlw= -github.com/fluxcd/pkg/runtime v0.40.0 h1:uGiiEbMZwd7xmbKaVmcH7iilCFW9betWbz0r1taK3G0= -github.com/fluxcd/pkg/runtime v0.40.0/go.mod h1:BqHEOVrZmt19p0q1OlGFWAYh3rZ28+IBpxLB2yPjjQ4= -github.com/fluxcd/pkg/sourceignore v0.3.4 h1:0cfS2Pj7xp2qpaerMjYqOBr82LC+/mGHl6v6pRbi5hs= -github.com/fluxcd/pkg/sourceignore v0.3.4/go.mod h1:ejLx+/uIrPUgqVzMTR5JiWuUnzs+zTkoEf9gS92LqaE= -github.com/fluxcd/pkg/ssa v0.28.2 h1:BL42JK9Cg17PHh/VIh64e6GZ+rjO47uDMGXGSGeCdao= -github.com/fluxcd/pkg/ssa v0.28.2/go.mod h1:nL0bCiFTAMvMrEKGlVCAgtLdP8VIPBGizY5xgasaypI= -github.com/fluxcd/pkg/ssh v0.8.0 h1:CqHIsWYfAtGxh2D6ZvzGTkFout6MaQnFpytPzJPbDLA= -github.com/fluxcd/pkg/ssh v0.8.0/go.mod h1:bo6HgWqIIuXU6r5HCxRFa7Uo7b4Nnzsz6MvdtAYn2XY= -github.com/fluxcd/pkg/tar v0.2.0 h1:HEUHgONQYsJGeZZ4x6h5nQU9Aox1I4T3bOp1faWTqf8= -github.com/fluxcd/pkg/tar v0.2.0/go.mod h1:w0/TOC7kwBJhnSJn7TCABkc/I7ib1f2Yz6vOsbLBnhw= -github.com/fluxcd/pkg/version v0.2.2 h1:ZpVXECeLA5hIQMft11iLp6gN3cKcz6UNuVTQPw/bRdI= -github.com/fluxcd/pkg/version v0.2.2/go.mod h1:NGnh/no8S6PyfCDxRFrPY3T5BUnqP48MxfxNRU0z8C0= -github.com/fluxcd/source-controller/api v1.0.1 h1:nycylbNBnQd+EO4UHpqXqAQJ1cGAPxgBbrfERCQ1pp8= -github.com/fluxcd/source-controller/api v1.0.1/go.mod h1:rAY5FRFGZUKpIFNyYANHIgPgJPvbALBHWsq/zHw/cXQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= +github.com/fluxcd/go-git-providers v0.20.1 h1:ER10UUup3y/lAyANvMjgaYI/9av/upetF2PTi3aCqvs= +github.com/fluxcd/go-git-providers v0.20.1/go.mod h1:FhBThaf3/kyKCBg4v0mKcQqQB2rPDv/L8baH3+nFtHc= +github.com/fluxcd/helm-controller/api v1.0.1 h1:Gn9qEVuif6D5+gHmVwTEZkR4+nmLOcOhKx4Sw2gL2EA= +github.com/fluxcd/helm-controller/api v1.0.1/go.mod h1:/6AD5a2qjo/ttxVM8GR33syLZwqigta60DCLdy8GrME= +github.com/fluxcd/image-automation-controller/api v0.38.0 h1:+phX67uf0INGDC4sghsPPNUiE8taVp7AcWgJH8LkiUk= +github.com/fluxcd/image-automation-controller/api v0.38.0/go.mod h1:FfWWRxG03514+MUNJ+uN6fXzjwdbqsJqCggukIZ1tx8= +github.com/fluxcd/image-reflector-controller/api v0.32.0 h1:mb/v9JzRHcjLcnGqmgsq0+yCcoOyae/TrOWae9T87PE= +github.com/fluxcd/image-reflector-controller/api v0.32.0/go.mod h1:Ap3/KK8MfQAdmuhakg9CweEa3Xwwmvausbqrgd3HBWY= +github.com/fluxcd/kustomize-controller/api v1.3.0 h1:IwXkU48lQ/YhU6XULlPXDgQlnpNyQdCNbUvhLdWVIbE= +github.com/fluxcd/kustomize-controller/api v1.3.0/go.mod h1:kg/WM9Uye5NOqGVW/F3jnkjrlgFZHHa84+4lnzOV8fI= +github.com/fluxcd/notification-controller/api v1.3.0 h1:e3Plvo44XIKP2pUjwx8U4/fMpPwVM3EvJrYLIIqcVrI= +github.com/fluxcd/notification-controller/api v1.3.0/go.mod h1:KUaWXACNwWpAYo/Q4mzBjGbsYlUzXdq654jc1XpgMQw= +github.com/fluxcd/pkg/apis/acl v0.3.0 h1:UOrKkBTOJK+OlZX7n8rWt2rdBmDCoTK+f5TY2LcZi8A= +github.com/fluxcd/pkg/apis/acl v0.3.0/go.mod h1:WVF9XjSMVBZuU+HTTiSebGAWMgM7IYexFLyVWbK9bNY= +github.com/fluxcd/pkg/apis/event v0.9.0 h1:iKxU+3v/3bAuC1C1iXg1mjbIiaEQet7WETh8lsfdcpY= +github.com/fluxcd/pkg/apis/event v0.9.0/go.mod h1:5LjcTeppPMEyOgtTbIP7q2GbVwIRUfujIxynIjHBV/k= +github.com/fluxcd/pkg/apis/kustomize v1.5.0 h1:ah4sfqccnio+/5Edz/tVz6LetFhiBoDzXAElj6fFCzU= +github.com/fluxcd/pkg/apis/kustomize v1.5.0/go.mod h1:nEzhnhHafhWOUUV8VMFLojUOH+HHDEsL75y54mt/c30= +github.com/fluxcd/pkg/apis/meta v1.5.0 h1:/G82d2Az5D9op3F+wJUpD8jw/eTV0suM6P7+cSURoUM= +github.com/fluxcd/pkg/apis/meta v1.5.0/go.mod h1:Y3u7JomuuKtr5fvP1Iji2/50FdRe5GcBug2jawNVkdM= +github.com/fluxcd/pkg/envsubst v1.1.0 h1:b0a9QsG36btk3MIWf7yM9FhVPhyXh6lLJu8eZk4Fyow= +github.com/fluxcd/pkg/envsubst v1.1.0/go.mod h1:4Uca9c2Bhu4+65sa6NbChEA3zZKhqyAjtgDEi+Zq9Y8= +github.com/fluxcd/pkg/git v0.19.0 h1:zIv+GAT0ieIUpnGBVi3Bhax/qq4Rr28BW7Jv4DTt6zE= +github.com/fluxcd/pkg/git v0.19.0/go.mod h1:wkqUOSrTjtsVVk/gC6/7RxVpi9GcqAA+7O5HVJF5S14= +github.com/fluxcd/pkg/git/gogit v0.19.0 h1:SdoNAmC/HTPXniQjp609X59rCsBiA+Sdq1Hv8SnYC6I= +github.com/fluxcd/pkg/git/gogit v0.19.0/go.mod h1:8kOmrNMjq8daQTVLhp6klhuoY8+s81gydM0MozDjaHM= +github.com/fluxcd/pkg/gittestserver v0.12.0 h1:QGbIVyje9U6urSAeDw3diKb/5wdA+Cnw1YJN+3Zflaw= +github.com/fluxcd/pkg/gittestserver v0.12.0/go.mod h1:Eh82e+kzKdhpafnUwR5oCBmxqAqhF5QuCn290AFntPM= +github.com/fluxcd/pkg/kustomize v1.11.0 h1:8YV4i6VCCxpXGlK+NzfNKbuhuSlK6Bfdr/Qv5jJgEtQ= +github.com/fluxcd/pkg/kustomize v1.11.0/go.mod h1:SfkN+DKgf8aLNoQtNuHBUEeB/uyC4nGzbbF+Ld0TmPU= +github.com/fluxcd/pkg/oci v0.37.1 h1:p4rfCHZlBWL+Q5Xey51iiBRmoje0IevCBT0/r8iae3M= +github.com/fluxcd/pkg/oci v0.37.1/go.mod h1:LrVuX6VACenJ5ycQJxec+I7YJegCsE4nzRUV+6RuxcY= +github.com/fluxcd/pkg/runtime v0.47.1 h1:Q1tAFsp92uurWyoEe52AmMC4k+6DYTPBrUQDs+nz/9c= +github.com/fluxcd/pkg/runtime v0.47.1/go.mod h1:97a+PqpWMgQsoqh91uH3EQz+/DC7Uxc8xcu/rDHFC5c= +github.com/fluxcd/pkg/sourceignore v0.7.0 h1:qQrB2o543wA1o4vgR62ufwkAaDp8+f8Wdj1HKDlmDrU= +github.com/fluxcd/pkg/sourceignore v0.7.0/go.mod h1:A4GuZt2seJJkBm3kMiIx9nheoYZs98KTMr/A6/2fIro= +github.com/fluxcd/pkg/ssa v0.39.1 h1:xPYRKqgqB5p+5jgz2xBkXCE/7i1FIOa+nA3Wr7Gu2Ek= +github.com/fluxcd/pkg/ssa v0.39.1/go.mod h1:AkhMoFxipMf3WoO3lkXjj2nHNIz6sA5yQ50aBodtxnk= +github.com/fluxcd/pkg/ssh v0.13.0 h1:lPU1Gst8XIz7AU2dhdqVFaaOWd54/O1LZu62vH4JB/s= +github.com/fluxcd/pkg/ssh v0.13.0/go.mod h1:J9eyirMd4s++tWG4euRRhmcthKX203GPHpzFpH++TP8= +github.com/fluxcd/pkg/tar v0.7.0 h1:xdg95f4DlzMgd4m+xPRXrX4NLb8P8b5SAqB19sDOLIs= +github.com/fluxcd/pkg/tar v0.7.0/go.mod h1:KLg1zMZF7sEncGA9LEsfkskbCMyLSEgrjBRXqFK++VE= +github.com/fluxcd/pkg/version v0.4.0 h1:3F6oeIZ+ug/f7pALIBhcUhfURel37EPPOn7nsGfsnOg= +github.com/fluxcd/pkg/version v0.4.0/go.mod h1:izVsSDxac81qWRmpOL9qcxZYx+zAN1ajoP5SidGP6PA= +github.com/fluxcd/source-controller/api v1.3.0 h1:Z5Lq0aJY87yg0cQDEuwGLKS60GhdErCHtsi546HUt10= +github.com/fluxcd/source-controller/api v1.3.0/go.mod h1:+tfd0vltjcVs/bbnq9AlYR9AAHSVfM/Z4v4TpQmdJf4= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= +github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= -github.com/go-git/go-git/v5 v5.7.1-0.20230702134234-dd4e2b7f4b01 h1:+yTyIn80JO1XGuLjgnUwnJDFpRw5dOVk6Yv5dAzi5dI= -github.com/go-git/go-git/v5 v5.7.1-0.20230702134234-dd4e2b7f4b01/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= +github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= -github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= -github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gonvenience/bunt v1.3.5 h1:wSQquifvwEWtzn27k1ngLfeLaStyt0k1b/K6TrlCNAs= github.com/gonvenience/bunt v1.3.5/go.mod h1:7ApqkVBEWvX04oJ28Q2WeI/BvJM6VtukaJAU/q/pTs8= -github.com/gonvenience/neat v1.3.12 h1:xwIyRbJcG9LgcDYys+HHLH9DqqHeQsUpS5CfBUeskbs= -github.com/gonvenience/neat v1.3.12/go.mod h1:8OljAIgPelN0uPPO94VBqxK+Kz98d6ZFwHDg5o/PfkE= +github.com/gonvenience/neat v1.3.13 h1:wRp1k0GX5EOpelNH3GyLaFy4SvnJ6k1U5SenmEWkXko= +github.com/gonvenience/neat v1.3.13/go.mod h1:aE3+z4XlTJ+RzlZxdFiAIIJc1ikYLALAWtX9LqjQ87Q= github.com/gonvenience/term v1.0.2 h1:qKa2RydbWIrabGjR/fegJwpW5m+JvUwFL8mLhHzDXn0= github.com/gonvenience/term v1.0.2/go.mod h1:wThTR+3MzWtWn7XGVW6qQ65uaVf8GHED98KmwpuEQeo= github.com/gonvenience/text v1.0.7 h1:YmIqmgTwxnACYCG59DykgMbomwteYyNhAmEUEJtPl14= github.com/gonvenience/text v1.0.7/go.mod h1:OAjH+mohRszffLY6OjgQcUXiSkbrIavooFpfIt1ZwAs= -github.com/gonvenience/wrap v1.1.2 h1:xPKxNwL1HCguwyM+HlP/1CIuc9LRd7k8RodLwe9YTZA= -github.com/gonvenience/wrap v1.1.2/go.mod h1:GiryBSXoI3BAAhbWD1cZVj7RZmtiu0ERi/6R6eJfslI= +github.com/gonvenience/wrap v1.2.0 h1:CwAoa60QIBVmQn/aUregAbk9FstEr17k9vCYpKF972c= +github.com/gonvenience/wrap v1.2.0/go.mod h1:iNijaTmFD8+ORmNp9iS+dSBcCJrmIwwyoYLUngToGdk= github.com/gonvenience/ytbx v1.4.4 h1:jQopwyaLsVGuwdxSiN4WkXjsEaFNPJ3V4lUj7eyEpzo= github.com/gonvenience/ytbx v1.4.4/go.mod h1:w37+MKCPcCMY/jpPNmEklD4xKqrOAVBO6kIWW2+uI6M= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= -github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE= -github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q= -github.com/google/go-github/v52 v52.0.0 h1:uyGWOY+jMQ8GVGSX8dkSwCzlehU3WfdxQ7GweO/JP7M= -github.com/google/go-github/v52 v52.0.0/go.mod h1:WJV6VEEUPuMo5pXqqa2ZCZEdbQqua4zAk2MZTIo+m+4= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= +github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go= +github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -307,19 +315,22 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= -github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/homeport/dyff v1.5.8 h1:CZvnYFh6Pr/c+d32EnWODAHRTctrDKkz0tFr6yTYCpU= -github.com/homeport/dyff v1.5.8/go.mod h1:S669ekLW2ttUp6lT1d0jIlmH+eAsP3psfl9K6oMIBeU= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= +github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= +github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= +github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= +github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/homeport/dyff v1.7.1 h1:B3KJUtnU53H2UryxGcfYKQPrde8VjjbwlHZbczH3giQ= +github.com/homeport/dyff v1.7.1/go.mod h1:iLe5b3ymc9xmHZNuJlNVKERE8L2isQMBLxFiTXcwZY0= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -335,14 +346,13 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -357,8 +367,6 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 h1:BXxTozrOU8zgC5dkpn3J6NTRdoP+hjok/e+ACr4Hibk= github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3/go.mod h1:x1uk6vxTiVuNt6S5R2UYgdhpj3oKojXvOXauHZ7dEnI= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -374,8 +382,6 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= @@ -384,11 +390,10 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= -github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/51gbeLHfKGNfgEQQIWrlbdaOsidbQ= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -401,88 +406,102 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/notaryproject/notation-core-go v1.0.2 h1:VEt+mbsgdANd9b4jqgmx2C7U0DmwynOuD2Nhxh3bANw= +github.com/notaryproject/notation-core-go v1.0.2/go.mod h1:2HkQzUwg08B3x9oVIztHsEh7Vil2Rj+tYgxH+JObLX4= +github.com/notaryproject/notation-go v1.1.0 h1:7WBeH8FGoA+GkeUwmBIBnlJc/PpdYaUKfiXu6ZZeEeg= +github.com/notaryproject/notation-go v1.1.0/go.mod h1:ZSk34URQar5fnWflaFByzpDvuefgZKm/mp8Q2tQpBaw= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= +github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= -github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= +github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= +github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= +github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= +github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= +github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= -github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= github.com/theckman/yacspin v0.13.12 h1:CdZ57+n0U6JMuh2xqjnjRq5Haj6v1ner2djtLQRzJr4= @@ -492,79 +511,93 @@ github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RV github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= -github.com/xanzy/go-gitlab v0.83.0 h1:37p0MpTPNbsTMKX/JnmJtY8Ch1sFiJzVF342+RvZEGw= -github.com/xanzy/go-gitlab v0.83.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= +github.com/xanzy/go-gitlab v0.101.0 h1:qRgvX8DNE19zRugB6rnnZMZ5ubhITSKPLNWEyc6UIPg= +github.com/xanzy/go-gitlab v0.101.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= -github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 h1:5/KzhcSqd4UgY51l17r7C5g/JiE6DRw1Vq7VJfQHuMc= -go.starlark.net v0.0.0-20221028183056-acb66ad56dd2/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.opentelemetry.io/contrib/exporters/autoexport v0.46.1 h1:ysCfPZB9AjUlMa1UHYup3c9dAOCMQX/6sxSfPBUoxHw= +go.opentelemetry.io/contrib/exporters/autoexport v0.46.1/go.mod h1:ha0aiYm+DOPsLHjh0zoQ8W8sLT+LJ58J3j47lGpSLrU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0/go.mod h1:qcTO4xHAxZLaLxPd60TdE88rxtItPHgHWqOhOGRr0as= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= +go.opentelemetry.io/otel/exporters/prometheus v0.44.0 h1:08qeJgaPC0YEBu2PQMbqU3rogTlyzpjhCI2b58Yn00w= +go.opentelemetry.io/otel/exporters/prometheus v0.44.0/go.mod h1:ERL2uIeBtg4TxZdojHUwzZfIFlUIjZtxubT5p4h1Gjg= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgYCza3PXRUGEyCB++y1sAqm6guWFesk= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= +go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.starlark.net v0.0.0-20231121155337-90ade8b19d09 h1:hzy3LFnSN8kuQK8h9tHl4ndF6UruMj47OqwqsS+/Ai4= +go.starlark.net v0.0.0-20231121155337-90ade8b19d09/go.mod h1:LcLNIzVOMp4oV+uusnpk+VU+SzXaJakUuBjoCSWH5dM= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -572,9 +605,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -584,151 +616,127 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= -gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.27.3 h1:yR6oQXXnUEBWEWcvPWS0jQL575KoAboQPfJAuKNrw5Y= -k8s.io/api v0.27.3/go.mod h1:C4BNvZnQOF7JA/0Xed2S+aUyJSfTGkGFxLXz9MnpIpg= -k8s.io/apiextensions-apiserver v0.27.3 h1:xAwC1iYabi+TDfpRhxh4Eapl14Hs2OftM2DN5MpgKX4= -k8s.io/apiextensions-apiserver v0.27.3/go.mod h1:BH3wJ5NsB9XE1w+R6SSVpKmYNyIiyIz9xAmBl8Mb+84= -k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= -k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= -k8s.io/cli-runtime v0.27.3 h1:h592I+2eJfXj/4jVYM+tu9Rv8FEc/dyCoD80UJlMW2Y= -k8s.io/cli-runtime v0.27.3/go.mod h1:LzXud3vFFuDFXn2LIrWnscPgUiEj7gQQcYZE2UPn9Kw= -k8s.io/client-go v0.27.3 h1:7dnEGHZEJld3lYwxvLl7WoehK6lAq7GvgjxpA3nv1E8= -k8s.io/client-go v0.27.3/go.mod h1:2MBEKuTo6V1lbKy3z1euEGnhPfGZLKTS9tiJ2xodM48= -k8s.io/component-base v0.27.3 h1:g078YmdcdTfrCE4fFobt7qmVXwS8J/3cI1XxRi/2+6k= -k8s.io/component-base v0.27.3/go.mod h1:JNiKYcGImpQ44iwSYs6dysxzR9SxIIgQalk4HaCNVUY= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= -k8s.io/kubectl v0.27.3 h1:HyC4o+8rCYheGDWrkcOQHGwDmyLKR5bxXFgpvF82BOw= -k8s.io/kubectl v0.27.3/go.mod h1:g9OQNCC2zxT+LT3FS09ZYqnDhlvsKAfFq76oyarBcq4= -k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= -k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/cli-utils v0.34.0 h1:zCUitt54f0/MYj/ajVFnG6XSXMhpZ72O/3RewIchW8w= -sigs.k8s.io/cli-utils v0.34.0/go.mod h1:EXyMwPMu9OL+LRnj0JEMsGG/fRvbgFadcVlSnE8RhFs= -sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= -sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= +k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= +k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= +k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= +k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= +k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/cli-runtime v0.30.0 h1:0vn6/XhOvn1RJ2KJOC6IRR2CGqrpT6QQF4+8pYpWQ48= +k8s.io/cli-runtime v0.30.0/go.mod h1:vATpDMATVTMA79sZ0YUCzlMelf6rUjoBzlp+RnoM+cg= +k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= +k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= +k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o= +k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 h1:SbdLaI6mM6ffDSJCadEaD4IkuPzepLDGlkd2xV0t1uA= +k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kubectl v0.30.0 h1:xbPvzagbJ6RNYVMVuiHArC1grrV5vSmmIcSZuCdzRyk= +k8s.io/kubectl v0.30.0/go.mod h1:zgolRw2MQXLPwmic2l/+iHs239L49fhSeICuMhQQXTI= +k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY= +k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.18.1 h1:RpWbigmuiylbxOCLy0tGnq1cU1qWPwNIQzoJk+QeJx4= +sigs.k8s.io/controller-runtime v0.18.1/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.13.4 h1:E38Hfx0G9R9v7vRgKshviPotJQETG0S2gD3JdHLCAsI= -sigs.k8s.io/kustomize/api v0.13.4/go.mod h1:Bkaavz5RKK6ZzP0zgPrB7QbpbBJKiHuD3BB0KujY7Ls= -sigs.k8s.io/kustomize/kyaml v0.14.2 h1:9WSwztbzwGszG1bZTziQUmVMrJccnyrLb5ZMKpJGvXw= -sigs.k8s.io/kustomize/kyaml v0.14.2/go.mod h1:AN1/IpawKilWD7V+YvQwRGUvuUOOWpjsHu6uHwonSF4= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/kustomize/api v0.17.1 h1:MYJBOP/yQ3/5tp4/sf6HiiMfNNyO97LmtnirH9SLNr4= +sigs.k8s.io/kustomize/api v0.17.1/go.mod h1:ffn5491s2EiNrJSmgqcWGzQUVhc/pB0OKNI0HsT/0tA= +sigs.k8s.io/kustomize/kyaml v0.17.0 h1:G2bWs03V9Ur2PinHLzTUJ8Ded+30SzXZKiO92SRDs3c= +sigs.k8s.io/kustomize/kyaml v0.17.0/go.mod h1:6lxkYF1Cv9Ic8g/N7I86cvxNc5iinUo/P2vKsHNmpyE= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/build/build.go b/internal/build/build.go index a9e55c6840..d8a8c3815b 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -29,7 +29,6 @@ import ( "sync" "time" - "github.com/fluxcd/pkg/ssa" "github.com/theckman/yacspin" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -45,6 +44,7 @@ import ( kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" "github.com/fluxcd/pkg/kustomize" runclient "github.com/fluxcd/pkg/runtime/client" + ssautil "github.com/fluxcd/pkg/ssa/utils" "sigs.k8s.io/kustomize/kyaml/filesys" "github.com/fluxcd/flux2/v2/internal/utils" @@ -80,6 +80,7 @@ type Builder struct { timeout time.Duration spinner *yacspin.Spinner dryRun bool + strictSubst bool } // BuilderOptionFunc is a function that configures a Builder @@ -103,7 +104,7 @@ func WithTimeout(timeout time.Duration) BuilderOptionFunc { func WithProgressBar() BuilderOptionFunc { return func(b *Builder) error { - // Add a spiner + // Add a spinner cfg := yacspin.Config{ Frequency: 100 * time.Millisecond, CharSet: yacspin.CharSets[59], @@ -158,6 +159,14 @@ func WithDryRun(dryRun bool) BuilderOptionFunc { } } +// WithStrictSubstitute sets the strict substitute flag +func WithStrictSubstitute(strictSubstitute bool) BuilderOptionFunc { + return func(b *Builder) error { + b.strictSubst = strictSubstitute + return nil + } +} + // WithIgnore sets ignore field func WithIgnore(ignore []string) BuilderOptionFunc { return func(b *Builder) error { @@ -251,13 +260,13 @@ func (b *Builder) Build() ([]*unstructured.Unstructured, error) { return nil, fmt.Errorf("kustomize build failed: %w", err) } - objects, err := ssa.ReadObjects(bytes.NewReader(resources)) + objects, err := ssautil.ReadObjects(bytes.NewReader(resources)) if err != nil { return nil, fmt.Errorf("kustomize build failed: %w", err) } if m := b.kustomization.Spec.CommonMetadata; m != nil { - ssa.SetCommonMetadata(objects, m.Labels, m.Annotations) + ssautil.SetCommonMetadata(objects, m.Labels, m.Annotations) } return objects, nil @@ -333,7 +342,7 @@ func (b *Builder) unMarshallKustomization() (*kustomizev1.Kustomization, error) k := &kustomizev1.Kustomization{} decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(data), len(data)) // check for kustomization in yaml with the same name and namespace - for !(k.Name == b.name && (k.Namespace == b.namespace || k.Namespace == "")) { + for { err = decoder.Decode(k) if err != nil { if err == io.EOF { @@ -343,6 +352,13 @@ func (b *Builder) unMarshallKustomization() (*kustomizev1.Kustomization, error) return nil, fmt.Errorf("failed to unmarshall kustomization file %s: %w", b.kustomizationFile, err) } } + + if strings.HasPrefix(k.APIVersion, kustomizev1.GroupVersion.Group+"/") && + k.Kind == kustomizev1.KustomizationKind && + k.Name == b.name && + (k.Namespace == b.namespace || k.Namespace == "") { + break + } } return k, nil } @@ -354,7 +370,7 @@ func (b *Builder) generate(kustomization kustomizev1.Kustomization, dirPath stri } // a scanner will be used down the line to parse the list - // so we have to make sure to unclude newlines + // so we have to make sure to include newlines ignoreList := strings.Join(b.ignore, "\n") gen := kustomize.NewGeneratorWithIgnore("", ignoreList, unstructured.Unstructured{Object: data}) @@ -368,7 +384,7 @@ func (b *Builder) generate(kustomization kustomizev1.Kustomization, dirPath stri func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomization, dirPath string) (resmap.ResMap, error) { fs := filesys.MakeFsOnDisk() - // acuire the lock + // acquire the lock b.mu.Lock() defer b.mu.Unlock() @@ -384,7 +400,13 @@ func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomizatio if err != nil { return nil, err } - outRes, err := kustomize.SubstituteVariables(ctx, b.client, unstructured.Unstructured{Object: data}, res, b.dryRun) + outRes, err := kustomize.SubstituteVariables(ctx, + b.client, + unstructured.Unstructured{Object: data}, + res, + kustomize.SubstituteWithDryRun(b.dryRun), + kustomize.SubstituteWithStrict(b.strictSubst), + ) if err != nil { return nil, fmt.Errorf("var substitution failed for '%s': %w", res.GetName(), err) } @@ -532,10 +554,8 @@ func maskDockerconfigjsonSopsData(dataMap map[string]string, encode bool) error func maskBase64EncryptedSopsData(dataMap map[string]string, mask string) error { for k, v := range dataMap { data, err := base64.StdEncoding.DecodeString(v) - if err != nil { - if _, ok := err.(base64.CorruptInputError); ok { - return err - } + if corruptErr := base64.CorruptInputError(0); errors.As(err, &corruptErr) { + return corruptErr } if bytes.Contains(data, []byte("sops")) && bytes.Contains(data, []byte("ENC[")) { @@ -557,9 +577,9 @@ func maskSopsDataInStringDataSecret(stringDataMap map[string]string, mask string } // Cancel cancels the build -// It restores a clean reprository +// It restores a clean repository func (b *Builder) Cancel() error { - // acuire the lock + // acquire the lock b.mu.Lock() defer b.mu.Unlock() diff --git a/internal/build/build_test.go b/internal/build/build_test.go index cb40c82c03..be62abc50c 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -189,6 +189,12 @@ func Test_unMarshallKustomization(t *testing.T) { wantErr: true, errString: "failed find kustomization with name", }, + { + name: "yaml containing other resource with same name as kustomization", + localKsFile: "testdata/local-kustomization/invalid-resource.yaml", + wantErr: true, + errString: "failed find kustomization with name", + }, } b := &Builder{ @@ -324,7 +330,10 @@ func Test_ResolveKustomization(t *testing.T) { }, } - b := &Builder{} + b := &Builder{ + name: "podinfo", + namespace: "flux-system", + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b.kustomizationFile = tt.localKsFile diff --git a/internal/build/diff.go b/internal/build/diff.go index c316df9f20..3aad9d105d 100644 --- a/internal/build/diff.go +++ b/internal/build/diff.go @@ -35,12 +35,13 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/errors" - "sigs.k8s.io/cli-utils/pkg/kstatus/polling" - "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/yaml" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling" + "github.com/fluxcd/cli-utils/pkg/object" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" "github.com/fluxcd/pkg/ssa" + ssautil "github.com/fluxcd/pkg/ssa/utils" "github.com/fluxcd/flux2/v2/pkg/printers" ) @@ -116,13 +117,14 @@ func (b *Builder) Diff() (string, bool, error) { if err != nil { return "", createdOrDrifted, err } - defer cleanupDir(tmpDir) err = diff(liveFile, mergedFile, &output) if err != nil { + cleanupDir(tmpDir) return "", createdOrDrifted, err } + cleanupDir(tmpDir) createdOrDrifted = true } @@ -144,7 +146,7 @@ func (b *Builder) Diff() (string, bool, error) { createdOrDrifted = true } for _, object := range staleObjects { - output.WriteString(writeString(fmt.Sprintf("â–º %s deleted\n", ssa.FmtUnstructured(object)), bunt.OrangeRed)) + output.WriteString(writeString(fmt.Sprintf("â–º %s deleted\n", ssautil.FmtUnstructured(object)), bunt.OrangeRed)) } } } @@ -167,13 +169,13 @@ func writeYamls(liveObject, mergedObject *unstructured.Unstructured) (string, st liveYAML, _ := yaml.Marshal(liveObject) liveFile := filepath.Join(tmpDir, "live.yaml") - if err := os.WriteFile(liveFile, liveYAML, 0644); err != nil { + if err := os.WriteFile(liveFile, liveYAML, 0o600); err != nil { return "", "", "", err } mergedYAML, _ := yaml.Marshal(mergedObject) mergedFile := filepath.Join(tmpDir, "merged.yaml") - if err := os.WriteFile(mergedFile, mergedYAML, 0644); err != nil { + if err := os.WriteFile(mergedFile, mergedYAML, 0o600); err != nil { return "", "", "", err } diff --git a/internal/build/testdata/local-kustomization/invalid-resource.yaml b/internal/build/testdata/local-kustomization/invalid-resource.yaml new file mode 100644 index 0000000000..51287766a0 --- /dev/null +++ b/internal/build/testdata/local-kustomization/invalid-resource.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: podinfo diff --git a/internal/flags/crds.go b/internal/flags/crds.go index aa7eee6185..81f6d604c8 100644 --- a/internal/flags/crds.go +++ b/internal/flags/crds.go @@ -20,7 +20,7 @@ import ( "fmt" "strings" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/flux2/v2/internal/utils" ) diff --git a/internal/flags/helm_chart_source.go b/internal/flags/helm_chart_source.go index 1ec99408ca..d55f1914e9 100644 --- a/internal/flags/helm_chart_source.go +++ b/internal/flags/helm_chart_source.go @@ -26,7 +26,7 @@ import ( "github.com/fluxcd/flux2/v2/internal/utils" ) -var supportedHelmChartSourceKinds = []string{sourcev1b2.HelmRepositoryKind, sourcev1.GitRepositoryKind, sourcev1b2.BucketKind} +var supportedHelmChartSourceKinds = []string{sourcev1.HelmRepositoryKind, sourcev1.GitRepositoryKind, sourcev1b2.BucketKind} type HelmChartSource struct { Kind string diff --git a/internal/flags/helm_chart_source_test.go b/internal/flags/helm_chart_source_test.go index 4590c3e9cd..0f4500ae24 100644 --- a/internal/flags/helm_chart_source_test.go +++ b/internal/flags/helm_chart_source_test.go @@ -23,7 +23,7 @@ import ( "fmt" "testing" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" ) func TestHelmChartSource_Set(t *testing.T) { diff --git a/internal/flags/local_helm_chart_source.go b/internal/flags/local_helm_chart_source.go new file mode 100644 index 0000000000..19d2e5305c --- /dev/null +++ b/internal/flags/local_helm_chart_source.go @@ -0,0 +1,70 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flags + +import ( + "fmt" + "strings" + + "github.com/fluxcd/flux2/v2/internal/utils" +) + +type LocalHelmChartSource struct { + Kind string + Name string +} + +func (s *LocalHelmChartSource) String() string { + if s.Name == "" { + return "" + } + return fmt.Sprintf("%s/%s", s.Kind, s.Name) +} + +func (s *LocalHelmChartSource) Set(str string) error { + if strings.TrimSpace(str) == "" { + return fmt.Errorf("no helm chart source given, please specify %s", + s.Description()) + } + + sourceKind, sourceName := utils.ParseObjectKindName(str) + if sourceKind == "" || sourceName == "" { + return fmt.Errorf("invalid helm chart source '%s', must be in format /", str) + } + cleanSourceKind, ok := utils.ContainsEqualFoldItemString(supportedHelmChartSourceKinds, sourceKind) + if !ok { + return fmt.Errorf("source kind '%s' is not supported, must be one of: %s", + sourceKind, strings.Join(supportedHelmChartSourceKinds, ", ")) + } + + s.Kind = cleanSourceKind + s.Name = sourceName + + return nil +} + +func (s *LocalHelmChartSource) Type() string { + return "helmChartSource" +} + +func (s *LocalHelmChartSource) Description() string { + return fmt.Sprintf( + "source that contains the chart in the format '/', "+ + "where kind must be one of: (%s)", + strings.Join(supportedHelmChartSourceKinds, ", "), + ) +} diff --git a/internal/flags/rsa_key_bits.go b/internal/flags/rsa_key_bits.go index e214e1bf6d..716a461771 100644 --- a/internal/flags/rsa_key_bits.go +++ b/internal/flags/rsa_key_bits.go @@ -39,6 +39,9 @@ func (b *RSAKeyBits) Set(str string) error { if err != nil { return err } + if bits < 1024 { + return fmt.Errorf("RSA key bit size must be at least 1024") + } if bits == 0 || bits%8 != 0 { return fmt.Errorf("RSA key bit size must be a multiples of 8") } @@ -51,5 +54,5 @@ func (b *RSAKeyBits) Type() string { } func (b *RSAKeyBits) Description() string { - return "SSH RSA public key bit size (multiplies of 8)" + return "SSH RSA public key bit size (multiplies of 8, min 1024)" } diff --git a/internal/flags/rsa_key_bits_test.go b/internal/flags/rsa_key_bits_test.go index d4bd296574..7199be7359 100644 --- a/internal/flags/rsa_key_bits_test.go +++ b/internal/flags/rsa_key_bits_test.go @@ -32,8 +32,8 @@ func TestRSAKeyBits_Set(t *testing.T) { }{ {"supported", "4096", "4096", false}, {"empty (default)", "", "2048", false}, - {"unsupported", "0", "0", true}, - {"unsupported", "123", "0", true}, + {"unsupported", "512", "0", true}, + {"unsupported", "1025", "0", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/flags/safe_relative_path.go b/internal/flags/safe_relative_path.go index dfd3b9ea4c..a58086e52d 100644 --- a/internal/flags/safe_relative_path.go +++ b/internal/flags/safe_relative_path.go @@ -54,5 +54,5 @@ func (p *SafeRelativePath) Type() string { } func (p *SafeRelativePath) Description() string { - return fmt.Sprintf("secure relative path") + return "secure relative path" } diff --git a/internal/flags/source_oci_verify_provider.go b/internal/flags/source_oci_verify_provider.go new file mode 100644 index 0000000000..acd57a9d2f --- /dev/null +++ b/internal/flags/source_oci_verify_provider.go @@ -0,0 +1,58 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flags + +import ( + "fmt" + "strings" + + "github.com/fluxcd/flux2/v2/internal/utils" +) + +var supportedSourceOCIVerifyProviders = []string{ + "cosign", +} + +type SourceOCIVerifyProvider string + +func (p *SourceOCIVerifyProvider) String() string { + return string(*p) +} + +func (p *SourceOCIVerifyProvider) Set(str string) error { + if strings.TrimSpace(str) == "" { + return fmt.Errorf("no source OCI verify provider given, please specify %s", + p.Description()) + } + if !utils.ContainsItemString(supportedSourceOCIVerifyProviders, str) { + return fmt.Errorf("source OCI verify provider '%s' is not supported, must be one of: %v", + str, strings.Join(supportedSourceOCIVerifyProviders, ", ")) + } + *p = SourceOCIVerifyProvider(str) + return nil +} + +func (p *SourceOCIVerifyProvider) Type() string { + return "sourceOCIVerifyProvider" +} + +func (p *SourceOCIVerifyProvider) Description() string { + return fmt.Sprintf( + "the OCI verify provider name to use for signature verification, available options are: (%s)", + strings.Join(supportedSourceOCIVerifyProviders, ", "), + ) +} diff --git a/internal/flags/source_oci_verify_provider_test.go b/internal/flags/source_oci_verify_provider_test.go new file mode 100644 index 0000000000..e8966521d5 --- /dev/null +++ b/internal/flags/source_oci_verify_provider_test.go @@ -0,0 +1,48 @@ +//go:build !e2e +// +build !e2e + +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flags + +import ( + "testing" +) + +func TestSourceOCIVerifyProvider_Set(t *testing.T) { + tests := []struct { + name string + str string + expect string + expectErr bool + }{ + {"supported", "cosign", "cosign", false}, + {"unsupported", "unsupported", "", true}, + {"empty", "", "", true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var s SourceOCIVerifyProvider + if err := s.Set(tt.str); (err != nil) != tt.expectErr { + t.Errorf("Set() error = %v, expectErr %v", err, tt.expectErr) + } + if str := s.String(); str != tt.expect { + t.Errorf("Set() = %v, expect %v", str, tt.expect) + } + }) + } +} diff --git a/internal/tree/tree.go b/internal/tree/tree.go index b651a19aa8..75c7ffd18b 100644 --- a/internal/tree/tree.go +++ b/internal/tree/tree.go @@ -22,8 +22,8 @@ package tree import ( "strings" - "github.com/fluxcd/pkg/ssa" - "sigs.k8s.io/cli-utils/pkg/object" + "github.com/fluxcd/cli-utils/pkg/object" + ssautil "github.com/fluxcd/pkg/ssa/utils" ) const ( @@ -74,7 +74,7 @@ func (t *objMetadataTree) AddTree(tree ObjMetadataTree) { } func (t *objMetadataTree) Text() string { - return ssa.FmtObjMetadata(t.Resource) + return ssautil.FmtObjMetadata(t.Resource) } func (t *objMetadataTree) Items() []ObjMetadataTree { diff --git a/internal/utils/apply.go b/internal/utils/apply.go index c910fce390..5867287ca5 100644 --- a/internal/utils/apply.go +++ b/internal/utils/apply.go @@ -27,13 +27,15 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/cli-runtime/pkg/genericclioptions" - "sigs.k8s.io/cli-utils/pkg/kstatus/polling" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/kustomize/api/konfig" - "github.com/fluxcd/flux2/v2/pkg/manifestgen/kustomization" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling" runclient "github.com/fluxcd/pkg/runtime/client" "github.com/fluxcd/pkg/ssa" + ssautil "github.com/fluxcd/pkg/ssa/utils" + + "github.com/fluxcd/flux2/v2/pkg/manifestgen/kustomization" ) // Apply is the equivalent of 'kubectl apply --server-side -f'. @@ -61,7 +63,7 @@ func Apply(ctx context.Context, rcg genericclioptions.RESTClientGetter, opts *ru var stageTwo []*unstructured.Unstructured for _, u := range objs { - if ssa.IsClusterDefinition(u) { + if ssautil.IsClusterDefinition(u) { stageOne = append(stageOne, u) } else { stageTwo = append(stageTwo, u) @@ -76,8 +78,10 @@ func Apply(ctx context.Context, rcg genericclioptions.RESTClientGetter, opts *ru changeSet.Append(cs.Entries) } - if err := waitForSet(rcg, opts, changeSet); err != nil { - return "", err + if len(changeSet.Entries) > 0 { + if err := waitForSet(rcg, opts, changeSet); err != nil { + return "", err + } } if len(stageTwo) > 0 { @@ -105,7 +109,7 @@ func readObjects(root, manifestPath string) ([]*unstructured.Unstructured, error if err != nil { return nil, err } - return ssa.ReadObjects(bytes.NewReader(resources)) + return ssautil.ReadObjects(bytes.NewReader(resources)) } ms, err := os.Open(manifestPath) @@ -114,7 +118,7 @@ func readObjects(root, manifestPath string) ([]*unstructured.Unstructured, error } defer ms.Close() - return ssa.ReadObjects(bufio.NewReader(ms)) + return ssautil.ReadObjects(bufio.NewReader(ms)) } func newManager(rcg genericclioptions.RESTClientGetter, opts *runclient.Options) (*ssa.ResourceManager, error) { diff --git a/internal/utils/objectutil.go b/internal/utils/objectutil.go deleted file mode 100644 index 35b381bc83..0000000000 --- a/internal/utils/objectutil.go +++ /dev/null @@ -1,63 +0,0 @@ -/* -Copyright 2023 The Flux authors -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// TODO: Remove this when -// https://github.com/kubernetes-sigs/controller-runtime/blob/c783d2527a7da76332a2d8d563a6ca0b80c12122/pkg/client/apiutil/apimachinery.go#L76-L104 -// is included in a semver release. - -package utils - -import ( - "errors" - "fmt" - - apimeta "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client/apiutil" -) - -// IsAPINamespaced returns true if the object is namespace scoped. -// For unstructured objects the gvk is found from the object itself. -func IsAPINamespaced(obj runtime.Object, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) { - gvk, err := apiutil.GVKForObject(obj, scheme) - if err != nil { - return false, err - } - - return IsAPINamespacedWithGVK(gvk, scheme, restmapper) -} - -// IsAPINamespacedWithGVK returns true if the object having the provided -// GVK is namespace scoped. -func IsAPINamespacedWithGVK(gk schema.GroupVersionKind, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) { - restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gk.Group, Kind: gk.Kind}) - if err != nil { - return false, fmt.Errorf("failed to get restmapping: %w", err) - } - - scope := restmapping.Scope.Name() - - if scope == "" { - return false, errors.New("scope cannot be identified, empty scope returned") - } - - if scope != apimeta.RESTScopeNameRoot { - return true, nil - } - return false, nil -} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index c5d8ec88ad..4a4787c2ad 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -41,12 +41,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" - imageautov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" + imageautov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" imagereflectv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" notificationv1 "github.com/fluxcd/notification-controller/api/v1" - notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3" "github.com/fluxcd/pkg/apis/meta" runclient "github.com/fluxcd/pkg/runtime/client" "github.com/fluxcd/pkg/version" @@ -133,7 +133,7 @@ func NewScheme() *apiruntime.Scheme { _ = kustomizev1.AddToScheme(scheme) _ = helmv2.AddToScheme(scheme) _ = notificationv1.AddToScheme(scheme) - _ = notificationv1b2.AddToScheme(scheme) + _ = notificationv1b3.AddToScheme(scheme) _ = imagereflectv1.AddToScheme(scheme) _ = imageautov1.AddToScheme(scheme) return scheme diff --git a/manifests/bases/helm-controller/kustomization.yaml b/manifests/bases/helm-controller/kustomization.yaml index 817f9c7d32..5f08e8473a 100644 --- a/manifests/bases/helm-controller/kustomization.yaml +++ b/manifests/bases/helm-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/helm-controller/releases/download/v0.35.0/helm-controller.crds.yaml -- https://github.com/fluxcd/helm-controller/releases/download/v0.35.0/helm-controller.deployment.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.0.1/helm-controller.crds.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.0.1/helm-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/helm-controller/patch.yaml b/manifests/bases/helm-controller/patch.yaml index 79773699fc..17f163d22e 100644 --- a/manifests/bases/helm-controller/patch.yaml +++ b/manifests/bases/helm-controller/patch.yaml @@ -7,3 +7,19 @@ - op: add path: /spec/template/spec/priorityClassName value: system-cluster-critical +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMAXPROCS + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.cpu +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMEMLIMIT + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.memory diff --git a/manifests/bases/image-automation-controller/kustomization.yaml b/manifests/bases/image-automation-controller/kustomization.yaml index 350faa1637..e5b6e3ed36 100644 --- a/manifests/bases/image-automation-controller/kustomization.yaml +++ b/manifests/bases/image-automation-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/image-automation-controller/releases/download/v0.35.0/image-automation-controller.crds.yaml -- https://github.com/fluxcd/image-automation-controller/releases/download/v0.35.0/image-automation-controller.deployment.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v0.38.0/image-automation-controller.crds.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v0.38.0/image-automation-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/image-automation-controller/patch.yaml b/manifests/bases/image-automation-controller/patch.yaml index 63d261ed12..73d0d8c73b 100644 --- a/manifests/bases/image-automation-controller/patch.yaml +++ b/manifests/bases/image-automation-controller/patch.yaml @@ -4,3 +4,19 @@ - op: add path: /spec/template/spec/serviceAccountName value: image-automation-controller +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMAXPROCS + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.cpu +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMEMLIMIT + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.memory diff --git a/manifests/bases/image-reflector-controller/kustomization.yaml b/manifests/bases/image-reflector-controller/kustomization.yaml index e13f3f025f..2cd1f1eab1 100644 --- a/manifests/bases/image-reflector-controller/kustomization.yaml +++ b/manifests/bases/image-reflector-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/image-reflector-controller/releases/download/v0.29.1/image-reflector-controller.crds.yaml -- https://github.com/fluxcd/image-reflector-controller/releases/download/v0.29.1/image-reflector-controller.deployment.yaml +- https://github.com/fluxcd/image-reflector-controller/releases/download/v0.32.0/image-reflector-controller.crds.yaml +- https://github.com/fluxcd/image-reflector-controller/releases/download/v0.32.0/image-reflector-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/image-reflector-controller/patch.yaml b/manifests/bases/image-reflector-controller/patch.yaml index 1db56bf2af..4f3e4a7f53 100644 --- a/manifests/bases/image-reflector-controller/patch.yaml +++ b/manifests/bases/image-reflector-controller/patch.yaml @@ -4,3 +4,19 @@ - op: add path: /spec/template/spec/serviceAccountName value: image-reflector-controller +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMAXPROCS + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.cpu +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMEMLIMIT + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.memory diff --git a/manifests/bases/kustomize-controller/kustomization.yaml b/manifests/bases/kustomize-controller/kustomization.yaml index 1ae7b51f42..8104b199d0 100644 --- a/manifests/bases/kustomize-controller/kustomization.yaml +++ b/manifests/bases/kustomize-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.0.1/kustomize-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.0.1/kustomize-controller.deployment.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.3.0/kustomize-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.3.0/kustomize-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/kustomize-controller/patch.yaml b/manifests/bases/kustomize-controller/patch.yaml index 6ce23139f1..7645b6849e 100644 --- a/manifests/bases/kustomize-controller/patch.yaml +++ b/manifests/bases/kustomize-controller/patch.yaml @@ -7,3 +7,19 @@ - op: add path: /spec/template/spec/priorityClassName value: system-cluster-critical +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMAXPROCS + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.cpu +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMEMLIMIT + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.memory diff --git a/manifests/bases/notification-controller/kustomization.yaml b/manifests/bases/notification-controller/kustomization.yaml index 582c5c5e4d..28481fdcc0 100644 --- a/manifests/bases/notification-controller/kustomization.yaml +++ b/manifests/bases/notification-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/notification-controller/releases/download/v1.0.0/notification-controller.crds.yaml -- https://github.com/fluxcd/notification-controller/releases/download/v1.0.0/notification-controller.deployment.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.3.0/notification-controller.crds.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.3.0/notification-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/notification-controller/patch.yaml b/manifests/bases/notification-controller/patch.yaml index 9c36ccdda5..fe3be22878 100644 --- a/manifests/bases/notification-controller/patch.yaml +++ b/manifests/bases/notification-controller/patch.yaml @@ -1,3 +1,19 @@ - op: add path: /spec/template/spec/serviceAccountName value: notification-controller +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMAXPROCS + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.cpu +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMEMLIMIT + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.memory diff --git a/manifests/bases/source-controller/kustomization.yaml b/manifests/bases/source-controller/kustomization.yaml index 3b8d6ec3fa..040e367a0b 100644 --- a/manifests/bases/source-controller/kustomization.yaml +++ b/manifests/bases/source-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/releases/download/v1.0.1/source-controller.crds.yaml -- https://github.com/fluxcd/source-controller/releases/download/v1.0.1/source-controller.deployment.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.3.0/source-controller.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.3.0/source-controller.deployment.yaml - account.yaml transformers: - labels.yaml diff --git a/manifests/bases/source-controller/patch.yaml b/manifests/bases/source-controller/patch.yaml index e55604ee63..1420c8e1bd 100644 --- a/manifests/bases/source-controller/patch.yaml +++ b/manifests/bases/source-controller/patch.yaml @@ -7,3 +7,19 @@ - op: add path: /spec/template/spec/priorityClassName value: system-cluster-critical +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMAXPROCS + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.cpu +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: GOMEMLIMIT + valueFrom: + resourceFieldRef: + containerName: manager + resource: limits.memory diff --git a/manifests/crds/kustomization.yaml b/manifests/crds/kustomization.yaml index 833252a6e3..93b2483375 100644 --- a/manifests/crds/kustomization.yaml +++ b/manifests/crds/kustomization.yaml @@ -1,9 +1,9 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/releases/download/v1.0.1/source-controller.crds.yaml -- https://github.com/fluxcd/kustomize-controller/releases/download/v1.0.1/kustomize-controller.crds.yaml -- https://github.com/fluxcd/helm-controller/releases/download/v0.35.0/helm-controller.crds.yaml -- https://github.com/fluxcd/notification-controller/releases/download/v1.0.0/notification-controller.crds.yaml -- https://github.com/fluxcd/image-reflector-controller/releases/download/v0.29.1/image-reflector-controller.crds.yaml -- https://github.com/fluxcd/image-automation-controller/releases/download/v0.35.0/image-automation-controller.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.3.0/source-controller.crds.yaml +- https://github.com/fluxcd/kustomize-controller/releases/download/v1.3.0/kustomize-controller.crds.yaml +- https://github.com/fluxcd/helm-controller/releases/download/v1.0.1/helm-controller.crds.yaml +- https://github.com/fluxcd/notification-controller/releases/download/v1.3.0/notification-controller.crds.yaml +- https://github.com/fluxcd/image-reflector-controller/releases/download/v0.32.0/image-reflector-controller.crds.yaml +- https://github.com/fluxcd/image-automation-controller/releases/download/v0.38.0/image-automation-controller.crds.yaml diff --git a/manifests/monitoring/README.md b/manifests/monitoring/README.md new file mode 100644 index 0000000000..af5d720b03 --- /dev/null +++ b/manifests/monitoring/README.md @@ -0,0 +1,22 @@ +# :warning: Removal Notice + +Starting Flux v2.1.0, released August 24, 2023, the Flux monitoring +configurations in this repository were marked as deprecated. The new monitoring +docs are available at [Flux monitoring](https://fluxcd.io/flux/monitoring/) +docs with new example configurations in +[fluxcd/flux2-monitoring-example](https://github.com/fluxcd/flux2-monitoring-example/). + +The deprecated configurations were removed in Flux v2.2 on December 13, 2023. All +users of these configurations are advised to use the new monitoring setup, +following the [docs](https://fluxcd.io/flux/monitoring/) and the +[examples](https://github.com/fluxcd/flux2-monitoring-example/). + +After collecting a lot of user feedback about our monitoring recommendation, in +order to serve most of the needs of the users, we decided to create a new +monitoring setup leveraging more of the kube-prometheus-stack, specifically +kube-state-metrics, to enable configuring Flux custom metrics, see the [Flux +custom Prometheus metrics](https://fluxcd.io/flux/monitoring/custom-metrics/) +docs to learn more about it. Please refer to +[fluxcd/flux2/4128](https://github.com/fluxcd/flux2/issues/4128) for a detailed +explanation about this change and the new capabilities offered by the new +monitoring setup. diff --git a/manifests/monitoring/kube-prometheus-stack/kustomization.yaml b/manifests/monitoring/kube-prometheus-stack/kustomization.yaml deleted file mode 100644 index 920eff1481..0000000000 --- a/manifests/monitoring/kube-prometheus-stack/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: monitoring -resources: - - namespace.yaml - - repository.yaml - - release.yaml diff --git a/manifests/monitoring/kube-prometheus-stack/namespace.yaml b/manifests/monitoring/kube-prometheus-stack/namespace.yaml deleted file mode 100644 index 78abcfce7a..0000000000 --- a/manifests/monitoring/kube-prometheus-stack/namespace.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: monitoring - labels: - app.kubernetes.io/component: monitoring diff --git a/manifests/monitoring/kube-prometheus-stack/release.yaml b/manifests/monitoring/kube-prometheus-stack/release.yaml deleted file mode 100644 index f2c60c5c1b..0000000000 --- a/manifests/monitoring/kube-prometheus-stack/release.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: helm.toolkit.fluxcd.io/v2beta1 -kind: HelmRelease -metadata: - name: kube-prometheus-stack -spec: - interval: 5m - chart: - spec: - version: "45.x" - chart: kube-prometheus-stack - sourceRef: - kind: HelmRepository - name: prometheus-community - interval: 60m - install: - crds: Create - upgrade: - crds: CreateReplace - # https://github.com/prometheus-community/helm-charts/blob/main/charts/kube-prometheus-stack/values.yaml - values: - alertmanager: - enabled: false - prometheus: - prometheusSpec: - retention: 24h - resources: - requests: - cpu: 200m - memory: 200Mi - podMonitorNamespaceSelector: {} - podMonitorSelector: - matchLabels: - app.kubernetes.io/component: monitoring - postRenderers: - - kustomize: - patches: - - target: - # Ignore these objects from Flux diff as they are mutated from chart hooks - kind: (ValidatingWebhookConfiguration|MutatingWebhookConfiguration) - name: kube-prometheus-stack-admission - patch: | - - op: add - path: /metadata/annotations/helm.toolkit.fluxcd.io~1driftDetection - value: disabled - - target: - # Ignore these objects from Flux diff as they are mutated at apply time but not at dry-run time - kind: PrometheusRule - patch: | - - op: add - path: /metadata/annotations/helm.toolkit.fluxcd.io~1driftDetection - value: disabled diff --git a/manifests/monitoring/kube-prometheus-stack/repository.yaml b/manifests/monitoring/kube-prometheus-stack/repository.yaml deleted file mode 100644 index 82d3607354..0000000000 --- a/manifests/monitoring/kube-prometheus-stack/repository.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: source.toolkit.fluxcd.io/v1beta2 -kind: HelmRepository -metadata: - name: prometheus-community -spec: - interval: 120m - # OCI builds for kube-prometheus-stack have been temporarily disabled (see https://github.com/prometheus-community/helm-charts/issues/2940). - type: default - url: https://prometheus-community.github.io/helm-charts diff --git a/manifests/monitoring/loki-stack/kustomization.yaml b/manifests/monitoring/loki-stack/kustomization.yaml deleted file mode 100644 index d6d4338281..0000000000 --- a/manifests/monitoring/loki-stack/kustomization.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: monitoring -resources: - - repository.yaml - - release.yaml diff --git a/manifests/monitoring/loki-stack/release.yaml b/manifests/monitoring/loki-stack/release.yaml deleted file mode 100644 index 5389e80811..0000000000 --- a/manifests/monitoring/loki-stack/release.yaml +++ /dev/null @@ -1,40 +0,0 @@ -apiVersion: helm.toolkit.fluxcd.io/v2beta1 -kind: HelmRelease -metadata: - name: loki-stack -spec: - interval: 5m - dependsOn: - - name: kube-prometheus-stack - chart: - spec: - version: "2.x" - chart: loki-stack - sourceRef: - kind: HelmRepository - name: grafana-charts - interval: 60m - # https://github.com/grafana/helm-charts/blob/main/charts/loki-stack/values.yaml - # https://github.com/grafana/loki/blob/main/production/helm/loki/values.yaml - values: - grafana: - enabled: false - sidecar: - datasources: - enabled: true - maxLines: 1000 - promtail: - enabled: true - loki: - enabled: true - isDefault: false - serviceMonitor: - enabled: true - additionalLabels: - app.kubernetes.io/part-of: kube-prometheus-stack - config: - chunk_store_config: - max_look_back_period: 0s - table_manager: - retention_deletes_enabled: true - retention_period: 12h diff --git a/manifests/monitoring/loki-stack/repository.yaml b/manifests/monitoring/loki-stack/repository.yaml deleted file mode 100644 index 49f58cdd1c..0000000000 --- a/manifests/monitoring/loki-stack/repository.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: source.toolkit.fluxcd.io/v1beta2 -kind: HelmRepository -metadata: - name: grafana-charts -spec: - interval: 120m0s - url: https://grafana.github.io/helm-charts diff --git a/manifests/monitoring/monitoring-config/dashboards/cluster.json b/manifests/monitoring/monitoring-config/dashboards/cluster.json deleted file mode 100644 index 1d49358673..0000000000 --- a/manifests/monitoring/monitoring-config/dashboards/cluster.json +++ /dev/null @@ -1,1006 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - }, - { - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "iconColor": "red", - "name": "flux events", - "target": { - "limit": 100, - "matchAny": false, - "tags": [ - "flux" - ], - "type": "tags" - } - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "iteration": 1652337714814, - "links": [], - "panels": [ - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - }, - { - "color": "red", - "value": 100 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 0 - }, - "id": 24, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "value" - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "exemplar": true, - "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\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Cluster Reconcilers", - "type": "stat" - }, - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 0 - }, - "id": 28, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "value" - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "exemplar": true, - "expr": "sum(gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"Kustomization|HelmRelease\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Failing Reconcilers", - "type": "stat" - }, - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - }, - { - "color": "red", - "value": 100 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 12, - "y": 0 - }, - "id": 29, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "value" - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "exemplar": true, - "expr": "count(gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"True\",kind=~\"GitRepository|HelmRepository|Bucket\"})\n-\nsum(gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"Deleted\",kind=~\"GitRepository|HelmRepository|Bucket\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Kubernetes Manifests Sources", - "type": "stat" - }, - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 18, - "y": 0 - }, - "id": 30, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "value" - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "exemplar": true, - "expr": "sum(gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"GitRepository|HelmRepository|Bucket\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Failing Sources", - "type": "stat" - }, - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "#EAB839", - "value": 1 - }, - { - "color": "red", - "value": 61 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 12, - "x": 0, - "y": 5 - }, - "id": 8, - "options": { - "displayMode": "gradient", - "minVizHeight": 10, - "minVizWidth": 0, - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "showUnfilled": true, - "text": {} - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "exemplar": true, - "expr": " sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind)", - "interval": "", - "legendFormat": "{{kind}}", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Reconciler ops avg. duration", - "type": "bargauge" - }, - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "#EAB839", - "value": 1 - }, - { - "color": "red", - "value": 61 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 12, - "x": 12, - "y": 5 - }, - "id": 31, - "options": { - "displayMode": "gradient", - "minVizHeight": 10, - "minVizWidth": 0, - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "showUnfilled": true, - "text": {} - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "exemplar": true, - "expr": " sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind)", - "interval": "", - "legendFormat": "{{kind}}", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Source ops avg. duration", - "type": "bargauge" - }, - { - "collapsed": false, - "datasource": "${DS_PROMETHEUS}", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 9 - }, - "id": 15, - "panels": [], - "title": "Status", - "type": "row" - }, - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "displayMode": "auto", - "filterable": true, - "inspect": false - }, - "mappings": [ - { - "options": { - "0": { - "text": "Ready" - }, - "1": { - "text": "Not Ready" - } - }, - "type": "value" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - }, - { - "color": "blue", - "value": 0 - }, - { - "color": "red", - "value": 1 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Status" - }, - "properties": [ - { - "id": "custom.displayMode", - "value": "color-background" - } - ] - } - ] - }, - "gridPos": { - "h": 11, - "w": 12, - "x": 0, - "y": 10 - }, - "id": 33, - "options": { - "footer": { - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [ - { - "desc": true, - "displayName": "Status" - } - ] - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "exemplar": true, - "expr": "gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"Kustomization|HelmRelease\"}", - "format": "table", - "instant": true, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Cluster reconciliation readiness ", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "__name__": true, - "app": true, - "container": true, - "endpoint": true, - "exported_namespace": false, - "instance": true, - "job": true, - "kubernetes_namespace": true, - "kubernetes_pod_name": true, - "namespace": true, - "pod": true, - "pod_template_hash": true, - "status": true, - "type": true - }, - "indexByName": {}, - "renameByName": { - "Value": "Status", - "exported_namespace": "Namespace", - "kind": "Kind", - "name": "Name", - "namespace": "Operator Namespace" - } - } - } - ], - "type": "table" - }, - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "displayMode": "auto", - "filterable": true, - "inspect": false - }, - "mappings": [ - { - "options": { - "0": { - "text": "Ready" - }, - "1": { - "text": "Not Ready" - } - }, - "type": "value" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - }, - { - "color": "blue", - "value": 0 - }, - { - "color": "red", - "value": 1 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Status" - }, - "properties": [ - { - "id": "custom.displayMode", - "value": "color-background" - } - ] - } - ] - }, - "gridPos": { - "h": 11, - "w": 12, - "x": 12, - "y": 10 - }, - "id": 34, - "options": { - "footer": { - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [ - { - "desc": true, - "displayName": "Status" - } - ] - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "exemplar": true, - "expr": "gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"GitRepository|HelmRepository|Bucket\"}", - "format": "table", - "instant": true, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Source acquisition readiness ", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "__name__": true, - "app": true, - "container": true, - "endpoint": true, - "exported_namespace": false, - "instance": true, - "job": true, - "kubernetes_namespace": true, - "kubernetes_pod_name": true, - "namespace": true, - "pod": true, - "pod_template_hash": true, - "status": true, - "type": true - }, - "indexByName": {}, - "renameByName": { - "Value": "Status", - "exported_namespace": "Namespace", - "kind": "Kind", - "name": "Name", - "namespace": "Operator Namespace" - } - } - } - ], - "type": "table" - }, - { - "collapsed": false, - "datasource": "${DS_PROMETHEUS}", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 21 - }, - "id": 17, - "panels": [], - "title": "Timing", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 22 - }, - "hiddenSeries": false, - "id": 27, - "legend": { - "alignAsTable": true, - "avg": true, - "current": false, - "hideEmpty": true, - "hideZero": true, - "max": false, - "min": false, - "rightSide": true, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.5", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": " sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind, name)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind, name)", - "hide": false, - "interval": "", - "legendFormat": "{{kind}}/{{name}}", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Cluster reconciliation duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 30 - }, - "hiddenSeries": false, - "id": 35, - "legend": { - "alignAsTable": true, - "avg": true, - "current": false, - "hideEmpty": true, - "hideZero": true, - "max": false, - "min": false, - "rightSide": true, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.5", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": " sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind, name)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind, name)", - "hide": false, - "interval": "", - "legendFormat": "{{kind}}/{{name}}", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Source acquisition duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "refresh": "30s", - "schemaVersion": 36, - "style": "light", - "tags": [ - "flux" - ], - "templating": { - "list": [ - { - "allValue": "", - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "$DS_PROMETHEUS", - "definition": "label_values(gotk_reconcile_condition, namespace)", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": null, - "multi": true, - "name": "operator_namespace", - "options": [], - "query": { - "query": "label_values(gotk_reconcile_condition, namespace)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 5, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": null, - "current": { - "selected": true, - "tags": [], - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "$DS_PROMETHEUS", - "definition": "label_values(gotk_reconcile_condition, exported_namespace)", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": null, - "multi": true, - "name": "namespace", - "options": [], - "query": { - "query": "label_values(gotk_reconcile_condition, exported_namespace)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "current": { - "selected": false, - "text": "Prometheus", - "value": "Prometheus" - }, - "hide": 0, - "includeAll": false, - "label": "Datasource", - "multi": false, - "name": "DS_PROMETHEUS", - "options": [], - "query": "prometheus", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "type": "datasource" - } - ] - }, - "time": { - "from": "now-15m", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] - }, - "title": "Flux Cluster Stats", - "uid": "flux-cluster", - "version": 3 -} diff --git a/manifests/monitoring/monitoring-config/dashboards/control-plane.json b/manifests/monitoring/monitoring-config/dashboards/control-plane.json deleted file mode 100644 index 40b1a1ad72..0000000000 --- a/manifests/monitoring/monitoring-config/dashboards/control-plane.json +++ /dev/null @@ -1,1319 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - }, - { - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "iconColor": "red", - "name": "flux events", - "target": { - "limit": 100, - "matchAny": false, - "tags": [ - "flux" - ], - "type": "tags" - } - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "gnetId": null, - "graphTooltip": 0, - "id": 29, - "iteration": 1639041352219, - "links": [], - "liveNow": false, - "panels": [ - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - }, - { - "color": "red", - "value": 100 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 0 - }, - "id": 24, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "value" - }, - "pluginVersion": "8.2.3", - "targets": [ - { - "expr": "sum(go_info{namespace=\"$namespace\",pod=~\".*-controller-.*\"})", - "interval": "", - "legendFormat": "pods", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Controllers", - "type": "stat" - }, - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - }, - { - "color": "#EAB839", - "value": 50 - }, - { - "color": "red", - "value": 100 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 0 - }, - "id": 23, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.2.3", - "targets": [ - { - "expr": "max(workqueue_longest_running_processor_seconds{namespace=\"$namespace\",pod=~\".*-controller-.*\"})", - "hide": false, - "interval": "", - "legendFormat": "seconds", - "refId": "B" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Max Work Queue", - "type": "stat" - }, - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - }, - { - "color": "#EAB839", - "value": 500000000 - }, - { - "color": "red", - "value": 900000000 - } - ] - }, - "unit": "decbits" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 12, - "y": 0 - }, - "id": 25, - "options": { - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "text": {} - }, - "pluginVersion": "8.2.3", - "targets": [ - { - "expr": "sum(go_memstats_alloc_bytes{namespace=\"$namespace\",pod=~\".*-controller-.*\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Memory", - "type": "gauge" - }, - { - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - }, - { - "color": "#EAB839", - "value": 50 - }, - { - "color": "red", - "value": 100 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 18, - "y": 0 - }, - "id": 26, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.2.3", - "targets": [ - { - "expr": "sum(rate(rest_client_requests_total{namespace=\"$namespace\",pod=~\".*-controller-.*\"}[1m]))", - "interval": "", - "legendFormat": "requests", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "API Requests", - "type": "stat" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "decimals": null, - "description": "", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 5 - }, - "hiddenSeries": false, - "id": 21, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.2.3", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(rest_client_requests_total{namespace=\"$namespace\"}[1m]))", - "hide": false, - "interval": "", - "legendFormat": "total", - "refId": "A" - }, - { - "expr": "sum(rate(rest_client_requests_total{namespace=\"$namespace\",code!~\"2..\"}[1m]))", - "hide": false, - "interval": "", - "legendFormat": "errors", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Kubernetes API Requests", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:912", - "decimals": null, - "format": "short", - "label": "", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:913", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "collapsed": false, - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 13 - }, - "id": 15, - "panels": [], - "title": "Resource Usage", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 11, - "w": 12, - "x": 0, - "y": 14 - }, - "hiddenSeries": false, - "id": 11, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": false, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.2.3", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "rate(process_cpu_seconds_total{namespace=\"$namespace\",pod=~\".*-controller-.*\"}[1m])", - "interval": "", - "legendFormat": "{{pod}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "CPU Usage", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:93", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:94", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 11, - "w": 12, - "x": 12, - "y": 14 - }, - "hiddenSeries": false, - "id": 13, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": false, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.2.3", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "sum(container_memory_working_set_bytes{namespace=\"$namespace\",container!=\"POD\",container!=\"\",pod=~\".*-controller-.*\"}) by (pod)", - "hide": false, - "interval": "", - "legendFormat": "{{pod}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Memory Usage", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:93", - "decimals": 0, - "format": "bytes", - "label": "", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:94", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "collapsed": false, - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 25 - }, - "id": 17, - "panels": [], - "title": "Reconciliation Stats", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 26 - }, - "hiddenSeries": false, - "id": 27, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.2.3", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "workqueue_longest_running_processor_seconds{name=\"kustomization\"}", - "hide": false, - "interval": "", - "legendFormat": "kustomizations", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Cluster Reconciliation Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:912", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:913", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": true, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "decimals": 2, - "description": "", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 34 - }, - "hiddenSeries": false, - "id": 2, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": false, - "linewidth": 1, - "nullPointMode": "null", - "percentage": false, - "pluginVersion": "8.2.3", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": true, - "targets": [ - { - "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"kustomization\",result!=\"error\"}[1m])) by (controller)", - "format": "time_series", - "interval": "", - "legendFormat": "successful reconciliations ", - "refId": "A" - }, - { - "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"kustomization\",result=\"error\"}[1m])) by (controller)", - "format": "time_series", - "interval": "", - "legendFormat": "failed reconciliations ", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Cluster Reconciliations ops/min", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:1171", - "format": "opm", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:1172", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": true, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "decimals": 2, - "description": "", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 34 - }, - "hiddenSeries": false, - "id": 4, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": false, - "linewidth": 1, - "nullPointMode": "null", - "percentage": false, - "pluginVersion": "8.2.3", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": true, - "targets": [ - { - "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"gitrepository\",result!=\"error\"}[1m]))", - "format": "time_series", - "interval": "", - "legendFormat": "successful git pulls", - "refId": "A" - }, - { - "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"gitrepository\",result=\"error\"}[1m]))", - "format": "time_series", - "interval": "", - "legendFormat": "failed git pulls", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Git Sources ops/min", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:285", - "format": "opm", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:286", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "collapsed": false, - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 43 - }, - "id": 19, - "panels": [], - "title": "Helm Stats", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 44 - }, - "hiddenSeries": false, - "id": 9, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": false, - "min": false, - "rightSide": true, - "show": false, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null as zero", - "percentage": false, - "pluginVersion": "8.2.3", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "histogram_quantile(0.50, sum(rate(controller_runtime_reconcile_time_seconds_bucket{controller=\"helmrelease\"}[5m])) by (le))", - "hide": true, - "interval": "", - "legendFormat": "P50", - "refId": "A" - }, - { - "expr": "histogram_quantile(0.90, sum(rate(controller_runtime_reconcile_time_seconds_bucket{controller=\"helmrelease\"}[5m])) by (le))", - "hide": true, - "interval": "", - "legendFormat": "P90", - "refId": "B" - }, - { - "expr": "histogram_quantile(0.99, sum(rate(controller_runtime_reconcile_time_seconds_bucket{controller=\"helmrelease\"}[5m])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P99", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Helm Release Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:1612", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:1613", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": true, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "decimals": 2, - "description": "", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 52 - }, - "hiddenSeries": false, - "id": 5, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": false, - "linewidth": 1, - "nullPointMode": "null", - "percentage": false, - "pluginVersion": "8.2.3", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": true, - "targets": [ - { - "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmrelease\",result!=\"error\"}[1m])) by (controller)", - "format": "time_series", - "interval": "", - "legendFormat": "successful reconciliations ", - "refId": "A" - }, - { - "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmrelease\",result=\"error\"}[1m])) by (controller)", - "format": "time_series", - "interval": "", - "legendFormat": "failed reconciliations ", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Helm Releases ops/min", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:1102", - "format": "opm", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:1103", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": true, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "decimals": 2, - "description": "", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 52 - }, - "hiddenSeries": false, - "id": 6, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": false, - "linewidth": 1, - "nullPointMode": "null", - "percentage": false, - "pluginVersion": "8.2.3", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": true, - "targets": [ - { - "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmchart\",result!=\"error\"}[1m])) by (controller)", - "format": "time_series", - "interval": "", - "legendFormat": "successful chart pulls", - "refId": "A" - }, - { - "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmchart\",result=\"error\"}[1m])) by (controller)", - "format": "time_series", - "interval": "", - "legendFormat": "failed chart pulls", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Helm Charts ops/min", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:1892", - "format": "opm", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:1893", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "refresh": "10s", - "schemaVersion": 31, - "style": "light", - "tags": [ - "flux" - ], - "templating": { - "list": [ - { - "current": { - "selected": false, - "text": "Prometheus", - "value": "Prometheus" - }, - "description": null, - "error": null, - "hide": 2, - "includeAll": false, - "label": null, - "multi": false, - "name": "DS_PROMETHEUS", - "options": [], - "query": "prometheus", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "type": "datasource" - }, - { - "allValue": null, - "current": { - "selected": false, - "text": "flux-system", - "value": "flux-system" - }, - "datasource": "${DS_PROMETHEUS}", - "definition": "workqueue_work_duration_seconds_count", - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": null, - "multi": false, - "name": "namespace", - "options": [], - "query": { - "query": "workqueue_work_duration_seconds_count", - "refId": "Prometheus-namespace-Variable-Query" - }, - "refresh": 2, - "regex": "/.*namespace=\"([^\"]*).*/", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-15m", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] - }, - "timezone": "", - "title": "Flux Control Plane", - "uid": "flux-control-plane", - "version": 2 -} \ No newline at end of file diff --git a/manifests/monitoring/monitoring-config/dashboards/logs.json b/manifests/monitoring/monitoring-config/dashboards/logs.json deleted file mode 100644 index 4d9f58dacc..0000000000 --- a/manifests/monitoring/monitoring-config/dashboards/logs.json +++ /dev/null @@ -1,332 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_LOKI", - "label": "Loki", - "description": "", - "type": "datasource", - "pluginId": "loki", - "pluginName": "Loki" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - }, - { - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "iconColor": "red", - "name": "flux events", - "target": { - "limit": 100, - "matchAny": false, - "tags": [ - "flux" - ], - "type": "tags" - } - } - ] - }, - "description": "Flux logs collected from Kubernetes, stored in Loki", - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 29, - "iteration": 1653748775696, - "links": [], - "liveNow": false, - "panels": [ - { - "datasource": "${DS_LOKI}", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "bars", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 4, - "options": { - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": "${DS_LOKI}", - "expr": "sum(count_over_time({namespace=~\"$namespace\", stream=~\"$stream\", app =~\"$controller\"} | json | __error__!=\"JSONParserErr\" | level=~\"$level\" |= \"$query\" [$__interval]))", - "instant": false, - "legendFormat": "Log count", - "range": true, - "refId": "A" - } - ], - "type": "timeseries" - }, - { - "datasource": "${DS_LOKI}", - "description": "Logs from services running in Kubernetes", - "gridPos": { - "h": 25, - "w": 24, - "x": 0, - "y": 4 - }, - "id": 2, - "options": { - "dedupStrategy": "numbers", - "enableLogDetails": false, - "prettifyLogMessage": true, - "showCommonLabels": false, - "showLabels": false, - "showTime": false, - "sortOrder": "Descending", - "wrapLogMessage": false - }, - "targets": [ - { - "datasource": "${DS_LOKI}", - "expr": "{namespace=~\"$namespace\", stream=~\"$stream\", app =~\"$controller\"} | json | __error__!=\"JSONParserErr\" | level=~\"$level\" |= \"$query\"", - "refId": "A" - } - ], - "type": "logs" - } - ], - "refresh": "10s", - "schemaVersion": 36, - "style": "light", - "tags": [ - "flux" - ], - "templating": { - "list": [ - { - "current": { - "selected": false, - "text": "", - "value": "" - }, - "description": "String to search for", - "hide": 0, - "label": "Search Query", - "name": "query", - "options": [ - { - "selected": true, - "text": "", - "value": "" - } - ], - "query": "", - "skipUrlSync": false, - "type": "textbox" - }, - { - "allValue": "info|error", - "current": { - "selected": false, - "text": "All", - "value": "$__all" - }, - "hide": 0, - "includeAll": true, - "multi": false, - "name": "level", - "options": [ - { - "selected": true, - "text": "All", - "value": "$__all" - }, - { - "selected": false, - "text": "info", - "value": "info" - }, - { - "selected": false, - "text": "error", - "value": "error" - } - ], - "query": "info,error", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" - }, - { - "allValue": ".+", - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "${DS_LOKI}", - "definition": "label_values(app)", - "hide": 0, - "includeAll": true, - "multi": true, - "name": "controller", - "options": [], - "query": "label_values(app)", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "allValue": ".+", - "current": { - "selected": true, - "text": [ - "flux-system" - ], - "value": [ - "flux-system" - ] - }, - "datasource": "${DS_LOKI}", - "definition": "label_values(namespace)", - "hide": 0, - "includeAll": true, - "multi": true, - "name": "namespace", - "options": [], - "query": "label_values(namespace)", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "allValue": ".+", - "current": { - "selected": false, - "text": "All", - "value": "$__all" - }, - "datasource": "${DS_LOKI}", - "definition": "label_values(stream)", - "hide": 0, - "includeAll": true, - "multi": true, - "name": "stream", - "options": [], - "query": "label_values(stream)", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": { - "selected": false, - "text": "Loki", - "value": "Loki" - }, - "hide": 0, - "includeAll": false, - "label": "Datasource", - "multi": false, - "name": "DS_LOKI", - "options": [], - "query": "loki", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "type": "datasource" - } - ] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Flux Logs", - "uid": "flux-logs", - "version": 2 -} diff --git a/manifests/monitoring/monitoring-config/kustomization.yaml b/manifests/monitoring/monitoring-config/kustomization.yaml deleted file mode 100644 index 29dcad853d..0000000000 --- a/manifests/monitoring/monitoring-config/kustomization.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: monitoring -resources: - - podmonitor.yaml -configMapGenerator: - - name: flux-grafana-dashboards - files: - - dashboards/control-plane.json - - dashboards/cluster.json - - dashboards/logs.json - options: - labels: - grafana_dashboard: "1" - app.kubernetes.io/part-of: flux - app.kubernetes.io/component: monitoring diff --git a/manifests/monitoring/monitoring-config/podmonitor.yaml b/manifests/monitoring/monitoring-config/podmonitor.yaml deleted file mode 100644 index 5e4b3a8c6f..0000000000 --- a/manifests/monitoring/monitoring-config/podmonitor.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: monitoring.coreos.com/v1 -kind: PodMonitor -metadata: - name: flux-system - namespace: flux-system - labels: - app.kubernetes.io/part-of: flux - app.kubernetes.io/component: monitoring -spec: - namespaceSelector: - matchNames: - - flux-system - selector: - matchExpressions: - - key: app - operator: In - values: - - helm-controller - - source-controller - - kustomize-controller - - notification-controller - - image-automation-controller - - image-reflector-controller - podMetricsEndpoints: - - port: http-prom - relabelings: - # https://github.com/prometheus-operator/prometheus-operator/issues/4816 - - sourceLabels: [__meta_kubernetes_pod_phase] - action: keep - regex: Running diff --git a/manifests/openshift/kustomization.yaml b/manifests/openshift/kustomization.yaml new file mode 100644 index 0000000000..a161f2b110 --- /dev/null +++ b/manifests/openshift/kustomization.yaml @@ -0,0 +1,48 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: flux-system +resources: + - namespace.yaml + - scc.yaml + - ../bases/source-controller + - ../bases/kustomize-controller + - ../bases/notification-controller + - ../bases/helm-controller + - ../bases/image-reflector-controller + - ../bases/image-automation-controller + - ../rbac + - ../policies +transformers: + - labels.yaml +images: + - name: fluxcd/source-controller + newName: ghcr.io/fluxcd/source-controller + - name: fluxcd/kustomize-controller + newName: ghcr.io/fluxcd/kustomize-controller + - name: fluxcd/helm-controller + newName: ghcr.io/fluxcd/helm-controller + - name: fluxcd/notification-controller + newName: ghcr.io/fluxcd/notification-controller + - name: fluxcd/image-reflector-controller + newName: ghcr.io/fluxcd/image-reflector-controller + - name: fluxcd/image-automation-controller + newName: ghcr.io/fluxcd/image-automation-controller +patches: + - patch: | + apiVersion: apps/v1 + kind: Deployment + metadata: + name: all + spec: + template: + spec: + securityContext: + $patch: delete + containers: + - name: manager + securityContext: + runAsUser: 65534 + seccompProfile: + $patch: delete + target: + kind: Deployment diff --git a/manifests/openshift/labels.yaml b/manifests/openshift/labels.yaml new file mode 100644 index 0000000000..5a5d78b4a0 --- /dev/null +++ b/manifests/openshift/labels.yaml @@ -0,0 +1,10 @@ +apiVersion: builtin +kind: LabelTransformer +metadata: + name: labels +labels: + app.kubernetes.io/part-of: flux + app.kubernetes.io/instance: flux-system +fieldSpecs: + - path: metadata/labels + create: true diff --git a/manifests/openshift/namespace.yaml b/manifests/openshift/namespace.yaml new file mode 100644 index 0000000000..c00a4321ea --- /dev/null +++ b/manifests/openshift/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: flux-system diff --git a/manifests/openshift/scc.yaml b/manifests/openshift/scc.yaml new file mode 100644 index 0000000000..6a25f70bde --- /dev/null +++ b/manifests/openshift/scc.yaml @@ -0,0 +1,43 @@ +# Allow Flux controllers to run as non-root on OpenShift +# Docs: https://fluxcd.io/flux/installation/configuration/openshift/ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: flux-scc +rules: + - apiGroups: + - security.openshift.io + resources: + - securitycontextconstraints + resourceNames: + - nonroot + verbs: + - use +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: flux-scc +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: flux-scc +subjects: + - kind: ServiceAccount + name: source-controller + namespace: flux-system + - kind: ServiceAccount + name: kustomize-controller + namespace: flux-system + - kind: ServiceAccount + name: helm-controller + namespace: flux-system + - kind: ServiceAccount + name: notification-controller + namespace: flux-system + - kind: ServiceAccount + name: image-reflector-controller + namespace: flux-system + - kind: ServiceAccount + name: image-automation-controller + namespace: flux-system diff --git a/manifests/rbac/controller.yaml b/manifests/rbac/controller.yaml index c444c58eda..b059891fbe 100644 --- a/manifests/rbac/controller.yaml +++ b/manifests/rbac/controller.yaml @@ -69,6 +69,11 @@ rules: - update - patch - delete +# required for flow control +- nonResourceURLs: + - /livez/ping + verbs: + - head --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/manifests/scripts/bundle.sh b/manifests/scripts/bundle.sh index c8f242c8fc..4259e02631 100755 --- a/manifests/scripts/bundle.sh +++ b/manifests/scripts/bundle.sh @@ -42,7 +42,7 @@ rm -rf $OUT_PATH mkdir -p $OUT_PATH files="" -info using "$(kustomize version --short)" +info using kustomize "$(kustomize version)" # build controllers for controller in ${IN_PATH}/bases/*/; do diff --git a/pkg/bootstrap/bootstrap.go b/pkg/bootstrap/bootstrap.go index 5d10b724fe..3faa921417 100644 --- a/pkg/bootstrap/bootstrap.go +++ b/pkg/bootstrap/bootstrap.go @@ -27,12 +27,17 @@ import ( apierr "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + apierrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" "github.com/fluxcd/pkg/apis/meta" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" "github.com/fluxcd/flux2/v2/pkg/manifestgen/install" "github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" @@ -43,6 +48,11 @@ var ( ErrReconciledWithWarning = errors.New("reconciled with warning") ) +// Reconciler reconciles and reports the health of different +// components and kubernetes resources involved in the installation of Flux. +// +// It is recommended use the `ReconcilerWithSyncCheck` interface that also +// reports the health of the GitRepository. type Reconciler interface { // ReconcileComponents reconciles the components by generating the // manifests with the provided values, committing them to Git and @@ -75,6 +85,14 @@ type RepositoryReconciler interface { ReconcileRepository(ctx context.Context) error } +// ReconcilerWithSyncCheck extends the Reconciler interface to also report the health of the GitReposiotry +// that syncs Flux on the cluster +type ReconcilerWithSyncCheck interface { + Reconciler + // ReportGitRepoHealth reports about the health of the GitRepository synchronizing the components. + ReportGitRepoHealth(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error +} + type PostGenerateSecretFunc func(ctx context.Context, secret corev1.Secret, options sourcesecret.Options) error func Run(ctx context.Context, reconciler Reconciler, manifestsBase string, @@ -98,18 +116,22 @@ func Run(ctx context.Context, reconciler Reconciler, manifestsBase string, return err } - var healthErrCount int + var errs []error + if r, ok := reconciler.(ReconcilerWithSyncCheck); ok { + if err := r.ReportGitRepoHealth(ctx, syncOpts, pollInterval, timeout); err != nil { + errs = append(errs, err) + } + } + if err := reconciler.ReportKustomizationHealth(ctx, syncOpts, pollInterval, timeout); err != nil { - healthErrCount++ + errs = append(errs, err) } + if err := reconciler.ReportComponentsHealth(ctx, installOpts, timeout); err != nil { - healthErrCount++ + errs = append(errs, err) } - if healthErrCount > 0 { - // Composing a "smart" error message here from the returned - // errors does not result in any useful information for the - // user, as both methods log the failures they run into. - err = fmt.Errorf("bootstrap failed with %d health check failure(s)", healthErrCount) + if len(errs) > 0 { + err = fmt.Errorf("bootstrap failed with %d health check failure(s): %w", len(errs), apierrors.NewAggregate(errs)) } return err @@ -151,6 +173,26 @@ func reconcileSecret(ctx context.Context, kube client.Client, secret corev1.Secr return kube.Update(ctx, &existing) } +func reconcileImagePullSecret(ctx context.Context, kube client.Client, installOpts install.Options) error { + credentials := strings.SplitN(installOpts.RegistryCredential, ":", 2) + dcj, err := sourcesecret.GenerateDockerConfigJson(installOpts.Registry, credentials[0], credentials[1]) + if err != nil { + return fmt.Errorf("failed to generate docker config json: %w", err) + } + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: installOpts.Namespace, + Name: installOpts.ImagePullSecret, + }, + StringData: map[string]string{ + corev1.DockerConfigJsonKey: string(dcj), + }, + Type: corev1.SecretTypeDockerConfigJson, + } + return reconcileSecret(ctx, kube, secret) +} + func kustomizationPathDiffers(ctx context.Context, kube client.Client, objKey client.ObjectKey, path string) (string, error) { var k kustomizev1.Kustomization if err := kube.Get(ctx, objKey, &k); err != nil { @@ -172,34 +214,47 @@ func kustomizationPathDiffers(ctx context.Context, kube client.Client, objKey cl return k.Spec.Path, nil } -func kustomizationReconciled(ctx context.Context, kube client.Client, objKey client.ObjectKey, - kustomization *kustomizev1.Kustomization, expectRevision string) func() (bool, error) { +type objectWithConditions interface { + client.Object + GetConditions() []metav1.Condition +} - return func() (bool, error) { - if err := kube.Get(ctx, objKey, kustomization); err != nil { +func objectReconciled(kube client.Client, objKey client.ObjectKey, clientObject objectWithConditions, expectRevision string) wait.ConditionWithContextFunc { + return func(ctx context.Context) (bool, error) { + // for some reason, TypeMeta gets unset after kube.Get so we want to store the GVK and set it after + // ref https://github.com/kubernetes-sigs/controller-runtime/issues/1517#issuecomment-844703142 + gvk := clientObject.GetObjectKind().GroupVersionKind() + if err := kube.Get(ctx, objKey, clientObject); err != nil { return false, err } + clientObject.GetObjectKind().SetGroupVersionKind(gvk) - // Detect suspended Kustomization, as this would result in an endless wait - if kustomization.Spec.Suspend { - return false, fmt.Errorf("Kustomization is suspended") + kind := gvk.Kind + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(clientObject) + if err != nil { + return false, err } - // Confirm the state we are observing is for the current generation - if kustomization.Generation != kustomization.Status.ObservedGeneration { - return false, nil + // Detect suspended object, as this would result in an endless wait + if suspended, ok, _ := unstructured.NestedBool(obj, "spec", "suspend"); ok && suspended { + return false, fmt.Errorf("%s '%s' is suspended", kind, objKey.String()) } - // Confirm the given revision has been attempted by the controller - if sourcev1.TransformLegacyRevision(kustomization.Status.LastAttemptedRevision) != expectRevision { + // Confirm the state we are observing is for the current generation + if generation, ok, _ := unstructured.NestedInt64(obj, "status", "observedGeneration"); ok && generation != clientObject.GetGeneration() { return false, nil } // Confirm the resource is healthy - if c := apimeta.FindStatusCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil { + if c := apimeta.FindStatusCondition(clientObject.GetConditions(), meta.ReadyCondition); c != nil { switch c.Status { case metav1.ConditionTrue: - return true, nil + // Confirm the given revision has been attempted by the controller + hasRev, err := hasRevision(kind, obj, expectRevision) + if err != nil { + return false, err + } + return hasRev, nil case metav1.ConditionFalse: return false, fmt.Errorf(c.Message) } @@ -208,6 +263,21 @@ func kustomizationReconciled(ctx context.Context, kube client.Client, objKey cli } } +// hasRevision checks that the reconciled revision (for Kustomization this is `.status.lastAttemptedRevision` +// and for Source APIs, it is stored in `.status.artifact.revision`) is the same as the expectedRev +func hasRevision(kind string, obj map[string]interface{}, expectedRev string) (bool, error) { + var rev string + switch kind { + case sourcev1.GitRepositoryKind, sourcev1b2.OCIRepositoryKind, sourcev1b2.BucketKind, sourcev1.HelmChartKind: + rev, _, _ = unstructured.NestedString(obj, "status", "artifact", "revision") + case kustomizev1.KustomizationKind: + rev, _, _ = unstructured.NestedString(obj, "status", "lastAttemptedRevision") + default: + return false, fmt.Errorf("cannot get status revision for kind: '%s'", kind) + } + return sourcev1b2.TransformLegacyRevision(rev) == expectedRev, nil +} + func retry(retries int, wait time.Duration, fn func() error) (err error) { for i := 0; ; i++ { err = fn() diff --git a/pkg/bootstrap/bootstrap_plain_git.go b/pkg/bootstrap/bootstrap_plain_git.go index e3f7bcd99d..78af0c8851 100644 --- a/pkg/bootstrap/bootstrap_plain_git.go +++ b/pkg/bootstrap/bootstrap_plain_git.go @@ -29,19 +29,23 @@ import ( "github.com/ProtonMail/go-crypto/openpgp" gogit "github.com/go-git/go-git/v5" corev1 "k8s.io/api/core/v1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/cli-runtime/pkg/genericclioptions" - "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/yaml" + "github.com/fluxcd/cli-utils/pkg/object" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/git/repository" "github.com/fluxcd/pkg/kustomize/filesys" runclient "github.com/fluxcd/pkg/runtime/client" + sourcev1 "github.com/fluxcd/source-controller/api/v1" "github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/pkg/log" @@ -170,11 +174,11 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest manifests.Path: strings.NewReader(manifests.Content), }), repository.WithSigner(signer)) if err != nil && err != git.ErrNoStagedFiles { - return fmt.Errorf("failed to commit sync manifests: %w", err) + return fmt.Errorf("failed to commit component manifests: %w", err) } if err == nil { - b.logger.Successf("committed sync manifests to %q (%q)", b.branch, commit) + b.logger.Successf("committed component manifests to %q (%q)", b.branch, commit) b.logger.Actionf("pushing component manifests to %q", b.url) if err = b.gitClient.Push(ctx, repository.PushConfig{}); err != nil { return fmt.Errorf("failed to push manifests: %w", err) @@ -203,6 +207,14 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest b.logger.Successf("installed components") } + // Reconcile image pull secret if needed + if options.ImagePullSecret != "" && options.RegistryCredential != "" { + if err := reconcileImagePullSecret(ctx, b.kube, options); err != nil { + return fmt.Errorf("failed to reconcile image pull secret: %w", err) + } + b.logger.Successf("reconciled image pull secret %s", options.ImagePullSecret) + } + b.logger.Successf("reconciled components") return nil } @@ -325,7 +337,7 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options return fmt.Errorf("failed to generate OpenPGP entity: %w", err) } } - commitMsg := fmt.Sprintf("Add Flux sync manifests") + commitMsg := "Add Flux sync manifests" if b.commitMessageAppendix != "" { commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix } @@ -397,21 +409,62 @@ func (b *PlainGitBootstrapper) ReportKustomizationHealth(ctx context.Context, op objKey := client.ObjectKey{Name: options.Name, Namespace: options.Namespace} - b.logger.Waitingf("waiting for Kustomization %q to be reconciled", objKey.String()) - expectRevision := fmt.Sprintf("%s@%s", options.Branch, git.Hash(head).Digest()) - var k kustomizev1.Kustomization - if err := wait.PollImmediate(pollInterval, timeout, kustomizationReconciled( - ctx, b.kube, objKey, &k, expectRevision), - ); err != nil { + b.logger.Waitingf("waiting for Kustomization %q to be reconciled", objKey.String()) + k := &kustomizev1.Kustomization{ + TypeMeta: metav1.TypeMeta{ + Kind: kustomizev1.KustomizationKind, + }, + } + if err := wait.PollUntilContextTimeout(ctx, pollInterval, timeout, true, + objectReconciled(b.kube, objKey, k, expectRevision)); err != nil { + // If the poll timed out, we want to log the ready condition message as + // that likely contains the reason + if errors.Is(err, context.DeadlineExceeded) { + readyCondition := apimeta.FindStatusCondition(k.Status.Conditions, meta.ReadyCondition) + if readyCondition != nil && readyCondition.Status != metav1.ConditionTrue { + err = fmt.Errorf("kustomization '%s' not ready: '%s'", objKey, readyCondition.Message) + } + } b.logger.Failuref(err.Error()) - return err + return fmt.Errorf("error while waiting for Kustomization to be ready: '%s'", err) } - b.logger.Successf("Kustomization reconciled successfully") return nil } +func (b *PlainGitBootstrapper) ReportGitRepoHealth(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error { + head, err := b.gitClient.Head() + if err != nil { + return err + } + + objKey := client.ObjectKey{Name: options.Name, Namespace: options.Namespace} + + b.logger.Waitingf("waiting for GitRepository %q to be reconciled", objKey.String()) + expectRevision := fmt.Sprintf("%s@%s", options.Branch, git.Hash(head).Digest()) + g := &sourcev1.GitRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: sourcev1.GitRepositoryKind, + APIVersion: sourcev1.GroupVersion.String(), + }, + } + if err := wait.PollUntilContextTimeout(ctx, pollInterval, timeout, true, + objectReconciled(b.kube, objKey, g, expectRevision)); err != nil { + // If the poll timed out, we want to log the ready condition message as + // that likely contains the reason + if errors.Is(err, context.DeadlineExceeded) { + readyCondition := apimeta.FindStatusCondition(g.Status.Conditions, meta.ReadyCondition) + if readyCondition != nil && readyCondition.Status != metav1.ConditionTrue { + err = fmt.Errorf("gitrepository '%s' not ready: '%s'", objKey, readyCondition.Message) + } + } + b.logger.Failuref(err.Error()) + return fmt.Errorf("error while waiting for GitRepository to be ready: '%s'", err) + } + b.logger.Successf("GitRepository reconciled successfully") + return nil +} func (b *PlainGitBootstrapper) ReportComponentsHealth(ctx context.Context, install install.Options, timeout time.Duration) error { cfg, err := utils.KubeConfig(b.restClientGetter, b.restClientOptions) if err != nil { @@ -465,9 +518,7 @@ func getOpenPgpEntity(keyRing openpgp.EntityList, passphrase, keyID string) (*op var entity *openpgp.Entity if keyID != "" { - if strings.HasPrefix(keyID, "0x") { - keyID = strings.TrimPrefix(keyID, "0x") - } + keyID = strings.TrimPrefix(keyID, "0x") if len(keyID) != 16 { return nil, fmt.Errorf("invalid GPG key id length; expected %d, got %d", 16, len(keyID)) } diff --git a/pkg/bootstrap/bootstrap_provider.go b/pkg/bootstrap/bootstrap_provider.go index d736f2f765..265ab0c1be 100644 --- a/pkg/bootstrap/bootstrap_provider.go +++ b/pkg/bootstrap/bootstrap_provider.go @@ -423,6 +423,11 @@ func (b *GitProviderBootstrapper) reconcileUserRepository(ctx context.Context) ( // branch correctly. Resort to Create until this has been resolved. repo, err = b.provider.UserRepositories().Create(ctx, repoRef, repoInfo) if err != nil { + var userErr *gitprovider.ErrIncorrectUser + if errors.As(err, &userErr) { + // return a better error message when the wrong owner is set + err = fmt.Errorf("the specified owner '%s' doesn't match the identity associated with the given token", b.owner) + } return nil, fmt.Errorf("failed to create new Git repository %q: %w", repoRef.String(), err) } b.logger.Successf("repository %q created", repoRef.String()) diff --git a/pkg/bootstrap/bootstrap_test.go b/pkg/bootstrap/bootstrap_test.go new file mode 100644 index 0000000000..47bf07f98d --- /dev/null +++ b/pkg/bootstrap/bootstrap_test.go @@ -0,0 +1,469 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bootstrap + +import ( + "context" + "testing" + + "github.com/fluxcd/pkg/apis/meta" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/fluxcd/flux2/v2/internal/utils" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" +) + +func Test_hasRevision(t *testing.T) { + var revision = "main@sha1:5bf3a8f9bb0aa5ae8afd6208f43757ab73fc033a" + + tests := []struct { + name string + obj objectWithConditions + rev string + expectErr bool + expectedBool bool + }{ + { + name: "Kustomization revision", + obj: &kustomizev1.Kustomization{ + TypeMeta: metav1.TypeMeta{ + Kind: kustomizev1.KustomizationKind, + }, + Status: kustomizev1.KustomizationStatus{ + LastAttemptedRevision: "main@sha1:5bf3a8f9bb0aa5ae8afd6208f43757ab73fc033a", + }, + }, + expectedBool: true, + }, + { + name: "GitRepository revision", + obj: &sourcev1.GitRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: sourcev1.GitRepositoryKind, + APIVersion: sourcev1.GroupVersion.String(), + }, + Status: sourcev1.GitRepositoryStatus{ + Artifact: &sourcev1.Artifact{ + Revision: "main@sha1:5bf3a8f9bb0aa5ae8afd6208f43757ab73fc033a", + }, + }, + }, + expectedBool: true, + }, + { + name: "GitRepository revision (wrong revision)", + obj: &sourcev1.GitRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: sourcev1.GitRepositoryKind, + APIVersion: sourcev1.GroupVersion.String(), + }, + Status: sourcev1.GitRepositoryStatus{ + Artifact: &sourcev1.Artifact{ + Revision: "main@sha1:e7f3a8f9bb0aa5ae8afd6208f43757ab73fc043a", + }, + }, + }, + }, + { + name: "Kustomization revision (empty revision)", + obj: &kustomizev1.Kustomization{ + TypeMeta: metav1.TypeMeta{ + Kind: kustomizev1.KustomizationKind, + }, + Status: kustomizev1.KustomizationStatus{ + LastAttemptedRevision: "", + }, + }, + }, + { + name: "OCIRepository revision", + obj: &sourcev1b2.OCIRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: sourcev1b2.OCIRepositoryKind, + }, + Status: sourcev1b2.OCIRepositoryStatus{ + Artifact: &sourcev1.Artifact{ + Revision: "main@sha1:5bf3a8f9bb0aa5ae8afd6208f43757ab73fc033a", + }, + }, + }, + expectedBool: true, + }, + { + name: "Alert revision(Not supported)", + obj: ¬ificationv1.Alert{ + TypeMeta: metav1.TypeMeta{ + Kind: notificationv1.AlertKind, + }, + Status: notificationv1.AlertStatus{ + ObservedGeneration: 1, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tt.obj) + g.Expect(err).To(BeNil()) + got, err := hasRevision(tt.obj.GetObjectKind().GroupVersionKind().Kind, obj, revision) + if tt.expectErr { + g.Expect(err).To(HaveOccurred()) + return + } + g.Expect(got).To(Equal(tt.expectedBool)) + }) + } +} + +func Test_objectReconciled(t *testing.T) { + expectedRev := "main@sha1:5bf3a8f9bb0aa5ae8afd6208f43757ab73fc033a" + + type updateStatus struct { + statusFn func(o client.Object) + expectedErr bool + expectedBool bool + } + tests := []struct { + name string + obj objectWithConditions + statuses []updateStatus + }{ + { + name: "GitRepository with no status", + obj: &sourcev1.GitRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: sourcev1.GitRepositoryKind, + APIVersion: sourcev1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + Namespace: "flux-system", + }, + }, + statuses: []updateStatus{ + { + expectedErr: false, + expectedBool: false, + }, + }, + }, + { + name: "suspended Kustomization", + obj: &kustomizev1.Kustomization{ + TypeMeta: metav1.TypeMeta{ + Kind: kustomizev1.KustomizationKind, + APIVersion: kustomizev1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + Namespace: "flux-system", + }, + Spec: kustomizev1.KustomizationSpec{ + Suspend: true, + }, + }, + statuses: []updateStatus{ + { + expectedErr: true, + }, + }, + }, + { + name: "Kustomization - status with old generation", + obj: &kustomizev1.Kustomization{ + TypeMeta: metav1.TypeMeta{ + Kind: kustomizev1.KustomizationKind, + APIVersion: kustomizev1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + Namespace: "flux-system", + Generation: 1, + }, + Status: kustomizev1.KustomizationStatus{ + ObservedGeneration: -1, + }, + }, + statuses: []updateStatus{ + { + expectedErr: false, + expectedBool: false, + }, + }, + }, + { + name: "GitRepository - status with same generation but no conditions", + obj: &sourcev1.GitRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: sourcev1.GitRepositoryKind, + APIVersion: sourcev1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + Namespace: "flux-system", + Generation: 1, + }, + Status: sourcev1.GitRepositoryStatus{ + ObservedGeneration: 1, + }, + }, + statuses: []updateStatus{ + { + expectedErr: false, + expectedBool: false, + }, + }, + }, + { + name: "GitRepository - status with conditions but no ready condition", + obj: &sourcev1.GitRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: sourcev1.GitRepositoryKind, + APIVersion: sourcev1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + Namespace: "flux-system", + Generation: 1, + }, + Status: sourcev1.GitRepositoryStatus{ + ObservedGeneration: 1, + Conditions: []metav1.Condition{ + {Type: meta.ReconcilingCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: "Progressing", Message: "Progressing"}, + }, + }, + }, + statuses: []updateStatus{ + { + expectedErr: false, + expectedBool: false, + }, + }, + }, + { + name: "Kustomization - status with false ready condition", + obj: &kustomizev1.Kustomization{ + TypeMeta: metav1.TypeMeta{ + Kind: kustomizev1.KustomizationKind, + APIVersion: kustomizev1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + Namespace: "flux-system", + Generation: 1, + }, + Status: kustomizev1.KustomizationStatus{ + ObservedGeneration: 1, + Conditions: []metav1.Condition{ + {Type: meta.ReadyCondition, Status: metav1.ConditionFalse, ObservedGeneration: 1, Reason: "Failing", Message: "Failed to clone"}, + }, + }, + }, + statuses: []updateStatus{ + { + expectedErr: true, + expectedBool: false, + }, + }, + }, + { + name: "Kustomization - status with true ready condition but different revision", + obj: &kustomizev1.Kustomization{ + TypeMeta: metav1.TypeMeta{ + Kind: kustomizev1.KustomizationKind, + APIVersion: kustomizev1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + Namespace: "flux-system", + Generation: 1, + }, + Status: kustomizev1.KustomizationStatus{ + ObservedGeneration: 1, + Conditions: []metav1.Condition{ + {Type: meta.ReadyCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: "Passing", Message: "Applied revision"}, + }, + LastAttemptedRevision: "main@sha1:e7f3a8f9bb0aa5ae8afd6208f43757ab73fc043a", + }, + }, + statuses: []updateStatus{ + { + expectedErr: false, + expectedBool: false, + }, + }, + }, + { + name: "GitRepository - status with true ready condition but different revision", + obj: &sourcev1.GitRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: sourcev1.GitRepositoryKind, + APIVersion: sourcev1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + Namespace: "flux-system", + Generation: 1, + }, + Status: sourcev1.GitRepositoryStatus{ + ObservedGeneration: 1, + Conditions: []metav1.Condition{ + {Type: meta.ReadyCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: "Readyyy", Message: "Cloned successfully"}, + }, + Artifact: &sourcev1.Artifact{ + Revision: "main@sha1:e7f3a8f9bb0aa5ae8afd6208f43757ab73fc043a", + }, + }, + }, + statuses: []updateStatus{ + { + expectedErr: false, + expectedBool: false, + }, + }, + }, + { + name: "GitRepository - ready with right revision", + obj: &sourcev1.GitRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: sourcev1.GitRepositoryKind, + APIVersion: sourcev1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + Namespace: "flux-system", + Generation: 1, + }, + Status: sourcev1.GitRepositoryStatus{ + ObservedGeneration: 1, + Conditions: []metav1.Condition{ + {Type: meta.ReadyCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: "Readyyy", Message: "Cloned successfully"}, + }, + Artifact: &sourcev1.Artifact{ + Revision: expectedRev, + }, + }, + }, + statuses: []updateStatus{ + { + expectedErr: false, + expectedBool: true, + }, + }, + }, + { + name: "GitRepository - sequence of status updates before ready", + obj: &sourcev1.GitRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: sourcev1.GitRepositoryKind, + APIVersion: sourcev1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + Namespace: "flux-system", + Generation: 1, + }, + }, + statuses: []updateStatus{ + { + // observed gen different + statusFn: func(o client.Object) { + gitRepo := o.(*sourcev1.GitRepository) + gitRepo.Status = sourcev1.GitRepositoryStatus{ + ObservedGeneration: -1, + } + }, + }, + { + // ready failing + statusFn: func(o client.Object) { + gitRepo := o.(*sourcev1.GitRepository) + gitRepo.Status = sourcev1.GitRepositoryStatus{ + ObservedGeneration: 1, + Conditions: []metav1.Condition{ + {Type: meta.ReadyCondition, Status: metav1.ConditionFalse, ObservedGeneration: 1, Reason: "Not Ready", Message: "Transient connection issue"}, + }, + } + }, + expectedErr: true, + }, + { + // updated to a different revision + statusFn: func(o client.Object) { + gitRepo := o.(*sourcev1.GitRepository) + gitRepo.Status = sourcev1.GitRepositoryStatus{ + ObservedGeneration: 1, + Conditions: []metav1.Condition{ + {Type: meta.ReadyCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: "Readyyy", Message: "Cloned successfully"}, + }, + Artifact: &sourcev1.Artifact{ + Revision: "wrong rev", + }, + } + }, + }, + { + // updated to the expected revision + statusFn: func(o client.Object) { + gitRepo := o.(*sourcev1.GitRepository) + gitRepo.Status = sourcev1.GitRepositoryStatus{ + ObservedGeneration: 1, + Conditions: []metav1.Condition{ + {Type: meta.ReadyCondition, Status: metav1.ConditionTrue, ObservedGeneration: 1, Reason: "Readyyy", Message: "Cloned successfully"}, + }, + Artifact: &sourcev1.Artifact{ + Revision: expectedRev, + }, + } + }, + expectedBool: true, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + builder := fake.NewClientBuilder().WithScheme(utils.NewScheme()) + builder.WithObjects(tt.obj) + + kubeClient := builder.Build() + + for _, updates := range tt.statuses { + if updates.statusFn != nil { + updates.statusFn(tt.obj) + g.Expect(kubeClient.Update(context.TODO(), tt.obj)).To(Succeed()) + } + + waitFunc := objectReconciled(kubeClient, client.ObjectKeyFromObject(tt.obj), tt.obj, expectedRev) + got, err := waitFunc(context.TODO()) + g.Expect(err != nil).To(Equal(updates.expectedErr)) + g.Expect(got).To(Equal(updates.expectedBool)) + } + }) + } +} diff --git a/pkg/bootstrap/provider/factory.go b/pkg/bootstrap/provider/factory.go index 575cb5518c..bd490334b4 100644 --- a/pkg/bootstrap/provider/factory.go +++ b/pkg/bootstrap/provider/factory.go @@ -19,6 +19,7 @@ package provider import ( "fmt" + "github.com/fluxcd/go-git-providers/gitea" "github.com/fluxcd/go-git-providers/github" "github.com/fluxcd/go-git-providers/gitlab" "github.com/fluxcd/go-git-providers/gitprovider" @@ -45,6 +46,17 @@ func BuildGitProvider(config Config) (gitprovider.Client, error) { if client, err = github.NewClient(opts...); err != nil { return nil, err } + case GitProviderGitea: + opts := []gitprovider.ClientOption{} + if config.Hostname != "" { + opts = append(opts, gitprovider.WithDomain(config.Hostname)) + } + if config.CaBundle != nil { + opts = append(opts, gitprovider.WithCustomCAPostChainTransportHook(config.CaBundle)) + } + if client, err = gitea.NewClient(config.Token, opts...); err != nil { + return nil, err + } case GitProviderGitLab: opts := []gitprovider.ClientOption{ gitprovider.WithConditionalRequests(true), diff --git a/pkg/bootstrap/provider/provider.go b/pkg/bootstrap/provider/provider.go index 4d1f92accb..f458184bfd 100644 --- a/pkg/bootstrap/provider/provider.go +++ b/pkg/bootstrap/provider/provider.go @@ -21,6 +21,7 @@ type GitProvider string const ( GitProviderGitHub GitProvider = "github" + GitProviderGitea GitProvider = "gitea" GitProviderGitLab GitProvider = "gitlab" GitProviderStash GitProvider = "stash" ) diff --git a/pkg/manifestgen/install/install.go b/pkg/manifestgen/install/install.go index 7742e04170..594997c2ca 100644 --- a/pkg/manifestgen/install/install.go +++ b/pkg/manifestgen/install/install.go @@ -27,6 +27,7 @@ import ( "time" securejoin "github.com/cyphar/filepath-securejoin" + "github.com/hashicorp/go-cleanhttp" "github.com/fluxcd/flux2/v2/pkg/manifestgen" ) @@ -91,7 +92,7 @@ func Generate(options Options, manifestsBase string) (*manifestgen.Manifest, err // GetLatestVersion calls the GitHub API and returns the latest released version. func GetLatestVersion() (string, error) { ghURL := "https://api.github.com/repos/fluxcd/flux2/releases/latest" - c := http.DefaultClient + c := cleanhttp.DefaultClient() c.Timeout = 15 * time.Second res, err := c.Get(ghURL) @@ -121,7 +122,7 @@ func ExistingVersion(version string) (bool, error) { } ghURL := fmt.Sprintf("https://api.github.com/repos/fluxcd/flux2/releases/tags/%s", version) - c := http.DefaultClient + c := cleanhttp.DefaultClient() c.Timeout = 15 * time.Second res, err := c.Get(ghURL) diff --git a/pkg/manifestgen/install/manifests.go b/pkg/manifestgen/install/manifests.go index 7e61f31cab..e48eac0a9b 100644 --- a/pkg/manifestgen/install/manifests.go +++ b/pkg/manifestgen/install/manifests.go @@ -26,6 +26,8 @@ import ( "path/filepath" "strings" + "github.com/hashicorp/go-cleanhttp" + "github.com/fluxcd/pkg/kustomize/filesys" "github.com/fluxcd/pkg/tar" @@ -44,7 +46,7 @@ func fetch(ctx context.Context, url, version, dir string) error { } // download - resp, err := http.DefaultClient.Do(req.WithContext(ctx)) + resp, err := cleanhttp.DefaultClient().Do(req.WithContext(ctx)) if err != nil { return fmt.Errorf("failed to download manifests.tar.gz from %s, error: %w", ghURL, err) } diff --git a/pkg/manifestgen/install/options.go b/pkg/manifestgen/install/options.go index a456007b8b..6b848eae29 100644 --- a/pkg/manifestgen/install/options.go +++ b/pkg/manifestgen/install/options.go @@ -26,6 +26,7 @@ type Options struct { ComponentsExtra []string EventsAddr string Registry string + RegistryCredential string ImagePullSecret string WatchAllNamespaces bool NetworkPolicy bool @@ -46,6 +47,7 @@ func MakeDefaultOptions() Options { ComponentsExtra: []string{"image-reflector-controller", "image-automation-controller"}, EventsAddr: "", Registry: "ghcr.io/fluxcd", + RegistryCredential: "", ImagePullSecret: "", WatchAllNamespaces: true, NetworkPolicy: true, diff --git a/pkg/manifestgen/kustomization/options.go b/pkg/manifestgen/kustomization/options.go index c8cf09550b..fbb7b981f9 100644 --- a/pkg/manifestgen/kustomization/options.go +++ b/pkg/manifestgen/kustomization/options.go @@ -16,7 +16,7 @@ limitations under the License. package kustomization -import "sigs.k8s.io/kustomize/api/filesys" +import "sigs.k8s.io/kustomize/kyaml/filesys" type Options struct { FileSystem filesys.FileSystem diff --git a/pkg/manifestgen/sourcesecret/options.go b/pkg/manifestgen/sourcesecret/options.go index ada27ce893..166ebb699b 100644 --- a/pkg/manifestgen/sourcesecret/options.go +++ b/pkg/manifestgen/sourcesecret/options.go @@ -33,13 +33,24 @@ const ( const ( UsernameSecretKey = "username" PasswordSecretKey = "password" - CAFileSecretKey = "caFile" - CertFileSecretKey = "certFile" - KeyFileSecretKey = "keyFile" + CACrtSecretKey = "ca.crt" + TLSCrtSecretKey = "tls.crt" + TLSKeySecretKey = "tls.key" PrivateKeySecretKey = "identity" PublicKeySecretKey = "identity.pub" KnownHostsSecretKey = "known_hosts" BearerTokenKey = "bearerToken" + TrustPolicyKey = "trustpolicy.json" + + // Deprecated: Replaced by CACrtSecretKey, but kept for backwards + // compatibility with deprecated TLS flags. + CAFileSecretKey = "caFile" + // Deprecated: Replaced by TLSCrtSecretKey, but kept for backwards + // compatibility with deprecated TLS flags. + CertFileSecretKey = "certFile" + // Deprecated: Replaced by TLSKeySecretKey, but kept for backwards + // compatibility with deprecated TLS flags. + KeyFileSecretKey = "keyFile" ) type Options struct { @@ -54,12 +65,29 @@ type Options struct { Keypair *ssh.KeyPair Username string Password string - CAFile []byte - CertFile []byte - KeyFile []byte + CACrt []byte + TLSCrt []byte + TLSKey []byte TargetPath string ManifestFile string BearerToken string + VerificationCrts []VerificationCrt + TrustPolicy []byte + + // Deprecated: Replaced by CACrt, but kept for backwards compatibility + // with deprecated TLS flags. + CAFile []byte + // Deprecated: Replaced by TLSCrt, but kept for backwards compatibility + // with deprecated TLS flags. + CertFile []byte + // Deprecated: Replaced by TLSKey, but kept for backwards compatibility + // with deprecated TLS flags. + KeyFile []byte +} + +type VerificationCrt struct { + Name string + CACrt []byte } func MakeDefaultOptions() Options { diff --git a/pkg/manifestgen/sourcesecret/sourcesecret.go b/pkg/manifestgen/sourcesecret/sourcesecret.go index 41849b70c6..380115ca3e 100644 --- a/pkg/manifestgen/sourcesecret/sourcesecret.go +++ b/pkg/manifestgen/sourcesecret/sourcesecret.go @@ -83,13 +83,13 @@ func Generate(options Options) (*manifestgen.Manifest, error) { var dockerCfgJson []byte if options.Registry != "" { - dockerCfgJson, err = generateDockerConfigJson(options.Registry, options.Username, options.Password) + dockerCfgJson, err = GenerateDockerConfigJson(options.Registry, options.Username, options.Password) if err != nil { return nil, fmt.Errorf("failed to generate json for docker config: %w", err) } } - secret := buildSecret(keypair, hostKey, options.CAFile, options.CertFile, options.KeyFile, dockerCfgJson, options) + secret := buildSecret(keypair, hostKey, dockerCfgJson, options) b, err := yaml.Marshal(secret) if err != nil { return nil, err @@ -130,7 +130,7 @@ func LoadKeyPair(privateKey []byte, password string) (*ssh.KeyPair, error) { }, nil } -func buildSecret(keypair *ssh.KeyPair, hostKey, caFile, certFile, keyFile, dockerCfg []byte, options Options) (secret corev1.Secret) { +func buildSecret(keypair *ssh.KeyPair, hostKey, dockerCfg []byte, options Options) (secret corev1.Secret) { secret.TypeMeta = metav1.TypeMeta{ APIVersion: "v1", Kind: "Secret", @@ -156,13 +156,19 @@ func buildSecret(keypair *ssh.KeyPair, hostKey, caFile, certFile, keyFile, docke secret.StringData[BearerTokenKey] = options.BearerToken } - if len(caFile) != 0 { - secret.StringData[CAFileSecretKey] = string(caFile) + if len(options.CACrt) != 0 { + secret.StringData[CACrtSecretKey] = string(options.CACrt) + } else if len(options.CAFile) != 0 { + secret.StringData[CAFileSecretKey] = string(options.CAFile) } - if len(certFile) != 0 && len(keyFile) != 0 { - secret.StringData[CertFileSecretKey] = string(certFile) - secret.StringData[KeyFileSecretKey] = string(keyFile) + if len(options.TLSCrt) != 0 && len(options.TLSKey) != 0 { + secret.Type = corev1.SecretTypeTLS + secret.StringData[TLSCrtSecretKey] = string(options.TLSCrt) + secret.StringData[TLSKeySecretKey] = string(options.TLSKey) + } else if len(options.CertFile) != 0 && len(options.KeyFile) != 0 { + secret.StringData[CertFileSecretKey] = string(options.CertFile) + secret.StringData[KeyFileSecretKey] = string(options.KeyFile) } if keypair != nil && len(hostKey) != 0 { @@ -175,6 +181,16 @@ func buildSecret(keypair *ssh.KeyPair, hostKey, caFile, certFile, keyFile, docke } } + if len(options.VerificationCrts) != 0 { + for _, crts := range options.VerificationCrts { + secret.StringData[crts.Name] = string(crts.CACrt) + } + } + + if len(options.TrustPolicy) != 0 { + secret.StringData[TrustPolicyKey] = string(options.TrustPolicy) + } + return } @@ -217,7 +233,7 @@ func resourceToString(data []byte) string { return string(data) } -func generateDockerConfigJson(url, username, password string) ([]byte, error) { +func GenerateDockerConfigJson(url, username, password string) ([]byte, error) { cred := fmt.Sprintf("%s:%s", username, password) auth := base64.StdEncoding.EncodeToString([]byte(cred)) cfg := DockerConfigJSON{ diff --git a/pkg/printers/table_printer.go b/pkg/printers/table_printer.go index 716166ab10..7f4f331e91 100644 --- a/pkg/printers/table_printer.go +++ b/pkg/printers/table_printer.go @@ -23,7 +23,7 @@ import ( "github.com/olekukonko/tablewriter" ) -// TablePrinter is a printer that prints Flux cmd outputs. +// TablePrinter is a printer that prints Flux cmd outputs. func TablePrinter(header []string) PrinterFunc { return func(w io.Writer, args ...interface{}) error { var rows [][]string @@ -35,9 +35,7 @@ func TablePrinter(header []string) PrinterFunc { if !ok { return fmt.Errorf("unsupported type %T", v) } - for i := range s { - rows = append(rows, s[i]) - } + rows = append(rows, s...) } default: return fmt.Errorf("unsupported type %T", arg) diff --git a/pkg/status/status.go b/pkg/status/status.go index f28b290c99..5f20e90fc8 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -24,14 +24,14 @@ import ( "time" "k8s.io/client-go/rest" - "sigs.k8s.io/cli-utils/pkg/kstatus/polling" - "sigs.k8s.io/cli-utils/pkg/kstatus/polling/aggregator" - "sigs.k8s.io/cli-utils/pkg/kstatus/polling/collector" - "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event" - "sigs.k8s.io/cli-utils/pkg/kstatus/status" - "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/aggregator" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/collector" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" runtimeclient "github.com/fluxcd/pkg/runtime/client" "github.com/fluxcd/flux2/v2/pkg/log" @@ -45,6 +45,16 @@ type StatusChecker struct { logger log.Logger } +func NewStatusCheckerWithClient(c client.Client, pollInterval time.Duration, timeout time.Duration, log log.Logger) (*StatusChecker, error) { + return &StatusChecker{ + pollInterval: pollInterval, + timeout: timeout, + client: c, + statusPoller: polling.NewStatusPoller(c, c.RESTMapper(), polling.Options{}), + logger: log, + }, nil +} + func NewStatusChecker(kubeConfig *rest.Config, pollInterval time.Duration, timeout time.Duration, log log.Logger) (*StatusChecker, error) { restMapper, err := runtimeclient.NewDynamicRESTMapper(kubeConfig) if err != nil { @@ -55,13 +65,7 @@ func NewStatusChecker(kubeConfig *rest.Config, pollInterval time.Duration, timeo return nil, err } - return &StatusChecker{ - pollInterval: pollInterval, - timeout: timeout, - client: c, - statusPoller: polling.NewStatusPoller(c, restMapper, polling.Options{}), - logger: log, - }, nil + return NewStatusCheckerWithClient(c, pollInterval, timeout, log) } func (sc *StatusChecker) Assess(identifiers ...object.ObjMetadata) error { @@ -94,7 +98,7 @@ func (sc *StatusChecker) Assess(identifiers ...object.ObjMetadata) error { } if coll.Error != nil || ctx.Err() == context.DeadlineExceeded { - return fmt.Errorf("timed out waiting for condition") + return fmt.Errorf("timed out waiting for all resources to be ready") } return nil } diff --git a/pkg/uninstall/uninstall.go b/pkg/uninstall/uninstall.go index 3293cc9cc9..5f657c041e 100644 --- a/pkg/uninstall/uninstall.go +++ b/pkg/uninstall/uninstall.go @@ -28,16 +28,17 @@ import ( "k8s.io/apimachinery/pkg/util/errors" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/fluxcd/flux2/v2/pkg/log" - "github.com/fluxcd/flux2/v2/pkg/manifestgen" - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" - autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" notificationv1 "github.com/fluxcd/notification-controller/api/v1" - notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2" + notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3" sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" + + "github.com/fluxcd/flux2/v2/pkg/log" + "github.com/fluxcd/flux2/v2/pkg/manifestgen" ) // Components removes all Kubernetes components that are part of Flux excluding the CRDs and namespace. @@ -48,7 +49,8 @@ func Components(ctx context.Context, logger log.Logger, kubeClient client.Client { var list appsv1.DeploymentList if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] if err := kubeClient.Delete(ctx, &r, opts); err != nil { logger.Failuref("Deployment/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error()) aggregateErr = append(aggregateErr, err) @@ -61,7 +63,8 @@ func Components(ctx context.Context, logger log.Logger, kubeClient client.Client { var list corev1.ServiceList if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] if err := kubeClient.Delete(ctx, &r, opts); err != nil { logger.Failuref("Service/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error()) aggregateErr = append(aggregateErr, err) @@ -74,7 +77,8 @@ func Components(ctx context.Context, logger log.Logger, kubeClient client.Client { var list networkingv1.NetworkPolicyList if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] if err := kubeClient.Delete(ctx, &r, opts); err != nil { logger.Failuref("NetworkPolicy/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error()) aggregateErr = append(aggregateErr, err) @@ -87,7 +91,8 @@ func Components(ctx context.Context, logger log.Logger, kubeClient client.Client { var list corev1.ServiceAccountList if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] if err := kubeClient.Delete(ctx, &r, opts); err != nil { logger.Failuref("ServiceAccount/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error()) aggregateErr = append(aggregateErr, err) @@ -100,7 +105,8 @@ func Components(ctx context.Context, logger log.Logger, kubeClient client.Client { var list rbacv1.ClusterRoleList if err := kubeClient.List(ctx, &list, selector); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] if err := kubeClient.Delete(ctx, &r, opts); err != nil { logger.Failuref("ClusterRole/%s deletion failed: %s", r.Name, err.Error()) aggregateErr = append(aggregateErr, err) @@ -113,7 +119,8 @@ func Components(ctx context.Context, logger log.Logger, kubeClient client.Client { var list rbacv1.ClusterRoleBindingList if err := kubeClient.List(ctx, &list, selector); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] if err := kubeClient.Delete(ctx, &r, opts); err != nil { logger.Failuref("ClusterRoleBinding/%s deletion failed: %s", r.Name, err.Error()) aggregateErr = append(aggregateErr, err) @@ -134,7 +141,8 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client { var list sourcev1.GitRepositoryList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -148,7 +156,8 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client { var list sourcev1b2.OCIRepositoryList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -160,9 +169,10 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client } } { - var list sourcev1b2.HelmRepositoryList + var list sourcev1.HelmRepositoryList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -174,9 +184,10 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client } } { - var list sourcev1b2.HelmChartList + var list sourcev1.HelmChartList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -190,7 +201,8 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client { var list sourcev1b2.BucketList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -204,7 +216,8 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client { var list kustomizev1.KustomizationList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -218,7 +231,8 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client { var list helmv2.HelmReleaseList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -230,9 +244,10 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client } } { - var list notificationv1b2.AlertList + var list notificationv1b3.AlertList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -244,9 +259,10 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client } } { - var list notificationv1b2.ProviderList + var list notificationv1b3.ProviderList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -260,7 +276,8 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client { var list notificationv1.ReceiverList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -274,7 +291,8 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client { var list imagev1.ImagePolicyList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -288,7 +306,8 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client { var list imagev1.ImageRepositoryList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -302,7 +321,8 @@ func Finalizers(ctx context.Context, logger log.Logger, kubeClient client.Client { var list autov1.ImageUpdateAutomationList if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] r.Finalizers = []string{} if err := kubeClient.Update(ctx, &r, opts); err != nil { logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) @@ -324,7 +344,8 @@ func CustomResourceDefinitions(ctx context.Context, logger log.Logger, kubeClien { var list apiextensionsv1.CustomResourceDefinitionList if err := kubeClient.List(ctx, &list, selector); err == nil { - for _, r := range list.Items { + for i := range list.Items { + r := list.Items[i] if err := kubeClient.Delete(ctx, &r, opts); err != nil { logger.Failuref("CustomResourceDefinition/%s deletion failed: %s", r.Name, err.Error()) aggregateErr = append(aggregateErr, err) diff --git a/rfcs/0002-helm-oci/README.md b/rfcs/0002-helm-oci/README.md index 518eaae548..d0636a3208 100644 --- a/rfcs/0002-helm-oci/README.md +++ b/rfcs/0002-helm-oci/README.md @@ -4,7 +4,7 @@ **Creation date:** 2022-03-30 -**Last update:** 2022-10-20 +**Last update:** 2023-11-28 ## Summary @@ -252,17 +252,15 @@ Bucket API design, where the same Kind servers different implementations: AWS S3 ## Design Details -In source-controller we'll add a new predicate for filtering `HelmRepositories` based on the `spec.type` field. +Unlike the default `HelmRepository`, the OCI `HelmRepository` does not need to +download any repository index file. The associated HelmChart can pull the chart +directly from the OCI registry based on the registry information in the +`HelmRepository` object. This makes the `HelmRepository` of type `oci` static, +not backed by a reconciler to move to a desired state. It becomes a data +container with information about the OCI registry. -The current `HelmRepositoryReconciler` will handle only objects with `type: default`, -it's scope remains unchanged. - -We'll introduce a new reconciler named `HelmRepositoryOCIReconciler`, that will handle -objects with `type: oci`. This reconciler will set the `HelmRepository` Ready status to -`False` if: -- the URL is not prefixed with `oci://` -- the URL is malformed and can't be parsed -- the specified credentials result in an authentication error +In source-controller, the `HelmRepositoryReconciler` will be updated to check +the `.spec.type` field of `HelmRepository` and do nothing if it is `oci`. The current `HelmChartReconciler` will be adapted to handle both types. @@ -277,6 +275,7 @@ The feature is enabled by default. * **2022-08-11** Resolve chart dependencies from OCI released with [flux2 v0.32.0](https://github.com/fluxcd/flux2/releases/tag/v0.32.0) * **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) * **2022-10-21** Verifying Helm charts with Cosign released with [flux2 v0.36.0](https://github.com/fluxcd/flux2/releases/tag/v0.36.0) +* **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) ### TODOs diff --git a/rfcs/0003-kubernetes-oci/README.md b/rfcs/0003-kubernetes-oci/README.md index 429b18803d..be3e7e63c4 100644 --- a/rfcs/0003-kubernetes-oci/README.md +++ b/rfcs/0003-kubernetes-oci/README.md @@ -4,7 +4,7 @@ **Creation date:** 2022-03-31 -**Last update:** 2023-02-20 +**Last update:** 2023-11-07 ## Summary @@ -232,14 +232,26 @@ spec: ``` For verifying public artifacts which are signed using the keyless method, -the `spec.verify.secretRef` field must be omitted: +the `.spec.verify.matchOIDCIdentity` field must be used instead of + `spec.verify.secretRef`. ```yaml spec: verify: provider: cosign + matchOIDCIdentity: + - issuer: "^https://token.actions.githubusercontent.com$" + subject: "^https://github.com/org/app-repository.*$" ``` +The `matchOIDCIdentity` entries must contain the following fields: + +- `.issuer`, regexp that matches against the OIDC issuer. +- `.subject`, regexp that matches against the subject identity in the certificate. + +The entries are evaluated in an OR fashion, i.e. the identity is deemed to be +verified if any one entry successfully matches against the identity. + When using the keyless method, Flux will verify the signatures in the Rekor transparency log instance hosted at [rekor.sigstore.dev](https://rekor.sigstore.dev/). @@ -470,3 +482,5 @@ The feature is enabled by default. * **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) * **2022-09-29** Verifying OCI artifacts with Cosign released with [flux2 v0.35.0](https://github.com/fluxcd/flux2/releases/tag/v0.35.0) * **2023-02-20** Custom OCI media types released with [flux2 v0.40.0](https://github.com/fluxcd/flux2/releases/tag/v0.40.0) +* **2023-10-31** OIDC identity verification implemented in + [source-controller#1250](https://github.com/fluxcd/source-controller/pull/1250) diff --git a/rfcs/0006-cdevents/CDEvents-Flux-RFC-Adapter.png b/rfcs/0006-cdevents/CDEvents-Flux-RFC-Adapter.png new file mode 100644 index 0000000000..270a98a8d1 Binary files /dev/null and b/rfcs/0006-cdevents/CDEvents-Flux-RFC-Adapter.png differ diff --git a/rfcs/0006-cdevents/Flux-CDEvents-RFC.png b/rfcs/0006-cdevents/Flux-CDEvents-RFC.png new file mode 100644 index 0000000000..8d49225e5c Binary files /dev/null and b/rfcs/0006-cdevents/Flux-CDEvents-RFC.png differ diff --git a/rfcs/0006-cdevents/README.md b/rfcs/0006-cdevents/README.md new file mode 100644 index 0000000000..e7017768a0 --- /dev/null +++ b/rfcs/0006-cdevents/README.md @@ -0,0 +1,109 @@ +# RFC-0006 Flux CDEvents Receiver + +**Status:** implementable + + + +**Creation date:** 2023-12-08 + +**Last update:** 2024-03-13 + +## Summary + +This RFC proposes to add a `Receiver` type to the Flux notification-controller API +for handling [CDEvents](https://cdevents.dev/). + +For `Receiver` objects configured to accept CDEvents, +notification-controller will verify the events sent to the receiver's webhook URL, +check that their type matches the expected type, and trigger the reconciliation +of the configured resources. + +## Motivation + +CDEvents enables interoperability between CI/CD tools in a workflow, and Flux is a +very popular continuous delivery tool, and consequently the CDF team received many questions +about integrating CDEvents with Flux. + +### Goals + +Allow Flux to receive CDEvents and trigger the reconciliation of resources based on the received events. + +### Non-Goals + +Make the Flux controllers emit CDEvents. + +## Proposal + +Add CDEvents to the list of available receivers in Flux notification-controller. +Similar to other receivers such as GitHub, Flux users will be able to use `spec.events` +in order to specify which event types the receiver will allow. +The receiver will also verify using the [CDEvents Go SDK](https://github.com/cdevents/sdk-go) that the +payload sent to the webhook URL is a valid CDEvent. + +### User Stories + +Users of multiple CI/CD tools such as Tekton and Flux +could use CDEvents as a way to enable interoperability. + +For example, a user may want a Flux resource to reconcile as part of a Tekton `pipeline`. +The Tekton `pipeline` will fire off a CDEvent to the CloudEvents Broker. +A subscription that the user will have set up externally, e.g. with the [knative broker](https://knative.dev/docs/eventing/brokers/), will then +send a relevant CDEvent to the Flux webhook receiver endpoint. + +![usecase](cdevents-flux-tekton.png) + +### Alternatives + +Certain use cases for CDEvents could be done alternatively using +available receivers such as the generic webhook. + +## Design Details + +Adding a Flux `Receiver` for CDEvents that works much like the other event-based receivers already implemented. + +The user will be able to define a Flux `Receiver` custom resource and deploy it to their cluster. +The receiver takes the payload sent to the webhook URL by an external events broker, +checks the headers for the event type, and filters out events based on the user-defined +list of events in `spec.events`. If left empty, it will act on all valid CDEvents. +It then validates the payload body using the [CDEvents Go SDK](https://github.com/cdevents/sdk-go). +Valid events will then trigger the reconciliation of all Flux objects specified in `.spec.resources`. + +The CDEvents broker is not a part of this design and is left to the users to set up however they wish. + +Example Receiver: + +```yaml +apiVersion: notification.toolkit.fluxcd.io/v1 +kind: Receiver +metadata: + name: cdevents-receiver + namespace: flux-system +spec: + type: cdevents + events: + - "dev.cdevents.change.merged" + secretRef: + name: receiver-token + resources: + - apiVersion: source.toolkit.fluxcd.io/v1 + kind: GitRepository + name: webapp + namespace: flux-system +``` + +![User Flowchart](Flux-CDEvents-RFC.png) + +![Adapter](CDEvents-Flux-RFC-Adapter.png) + + +## Implementation History + + diff --git a/rfcs/0006-cdevents/cdevents-flux-tekton.png b/rfcs/0006-cdevents/cdevents-flux-tekton.png new file mode 100644 index 0000000000..72223a0a1e Binary files /dev/null and b/rfcs/0006-cdevents/cdevents-flux-tekton.png differ diff --git a/tests/.gitignore b/tests/.gitignore index 3ca910aa75..0acf4705b3 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -5,11 +5,14 @@ ### Terraform ### # Local .terraform directories **/.terraform/* +*.terraform.lock.hcl + +# test files +build/ # .tfstate files *.tfstate *.tfstate.* - # Crash log files crash.log @@ -37,4 +40,5 @@ override.tf.json .terraformrc terraform.rc +.env # End of https://www.toptal.com/developers/gitignore/api/terraform diff --git a/tests/azure/README.md b/tests/azure/README.md deleted file mode 100644 index 7a51fd5dc7..0000000000 --- a/tests/azure/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# Azure E2E - -The test suite goal is to verify that Flux integration with Azure services are working properly. - -## Architecture - -The tests are run with the help of pre-configured Azure subscriptions and Azure DevOps organization. -Access to those accounts are currently limited to Flux maintainers. -* [Azure Subscription](https://portal.azure.com/#@weaveworksendtoend.onmicrosoft.com/resource/subscriptions/71e8dce4-9af6-405a-8e96-425f5d3c302b/overview) -* [Azure DevOps organization](https://dev.azure.com/flux-azure/) - -All infrastructure is and should be created with Terraform. There are two separate Terraform states. -All state should be configured to use remote state in Azure. -They should all be placed in the [same container](https://portal.azure.com/#@weaveworksendtoend.onmicrosoft.com/resource/subscriptions/71e8dce4-9af6-405a-8e96-425f5d3c302b/resourceGroups/terraform-state/providers/Microsoft.Storage/storageAccounts/terraformstate0419/containersList) -but use different keys. - -The [shared](./terraform/shared) Terraform creates long running cheaper infrastructure that is used across all tests. -This includes a Key Vault which contains an ssh key and Azure DevOps Personal Access Token -which cannot be created automatically. It also includes an Azure Container Registry which the -forked [podinfo](https://dev.azure.com/flux-azure/e2e/_git/podinfo) repository pushes -a Helm Chart and Docker image to. - -The [aks](./terraform/aks) Terraform creates the AKS cluster and related resources to run the tests. -It creates the AKS cluster, Azure DevOps repositories, Key Vault Key for Sops, and Azure EventHub. -The resources should be created and destroyed before and after every test run. Currently, -the same state is reused between runs to make sure that resources are left running after each test run. - -## Tests - -Each test run is initiated by running `terraform apply` on the aks Terraform, it does this by using the library -[terraform-exec](https://github.com/hashicorp/terraform-exec). It then reads the output of the Terraform to get -credentials and ssh keys, this means that a lot of the communication with the Azure API is offset to -Terraform instead of requiring it to be implemented in the test. - -The following tests are currently implemented: - -- [x] Flux can be successfully installed on AKS using the CLI e.g.: -- [x] source-controller can clone Azure DevOps repositories (https+ssh) -- [x] image-reflector-controller can list tags from Azure Container Registry image repositories -- [x] kustomize-controller can decrypt secrets using SOPS and Azure Key Vault -- [x] image-automation-controller can create branches and push to Azure DevOps repositories (https+ssh) -- [x] notification-controller can send commit status to Azure DevOps -- [x] notification-controller can forward events to Azure Event Hub -- [x] source-controller can pull charts from Azure Container Registry Helm repositories - -## Give User Access - -There are a couple of steps required when adding a new user to get access to the Azure portal and Azure DevOps. -To begin with add the new user to[Azure AD](https://portal.azure.com/#blade/Microsoft_AAD_IAM/UsersManagementMenuBlade/MsGraphUsers), -and add the user to the [Azure AD group flux-contributors](https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupDetailsMenuBlade/Overview/groupId/24e0f3f6-6555-4d3d-99ab-414c869cab5d). -The new users should now go through the invite process, and be able to sign in to both the Azure Portal and Azure DevOps. - -After the new user has signed into Azure DevOps you will need to modify the users permissions. -This cannot be done before the user has signed in a first time. -In the [organization users page](https://dev.azure.com/flux-azure/_settings/users) -chose "Manage User" and set the "Access Level" to basic -and make the user "Project Contributor" of the "e2e" Azure DevOps project. diff --git a/tests/azure/azure_test.go b/tests/azure/azure_test.go deleted file mode 100644 index 6209ec9b5b..0000000000 --- a/tests/azure/azure_test.go +++ /dev/null @@ -1,960 +0,0 @@ -/* -Copyright 2021 The Flux authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package test - -import ( - "bytes" - "context" - b64 "encoding/base64" - "encoding/json" - "fmt" - "io" - "log" - "os" - "path/filepath" - "strings" - "testing" - "time" - - eventhub "github.com/Azure/azure-event-hubs-go/v3" - install "github.com/hashicorp/hc-install" - "github.com/hashicorp/hc-install/fs" - "github.com/hashicorp/hc-install/product" - "github.com/hashicorp/hc-install/src" - "github.com/hashicorp/terraform-exec/tfexec" - "github.com/microsoft/azure-devops-go-api/azuredevops" - "github.com/microsoft/azure-devops-go-api/azuredevops/git" - "github.com/stretchr/testify/require" - giturls "github.com/whilp/git-urls" - "go.uber.org/multierr" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/klog/v2/klogr" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - runtimeLog "sigs.k8s.io/controller-runtime/pkg/log" - - automationv1beta1 "github.com/fluxcd/image-automation-controller/api/v1beta1" - reflectorv1beta2 "github.com/fluxcd/image-reflector-controller/api/v1beta2" - kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" - notiv1 "github.com/fluxcd/notification-controller/api/v1" - notiv1beta2 "github.com/fluxcd/notification-controller/api/v1beta2" - eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" - "github.com/fluxcd/pkg/apis/meta" - sourcev1 "github.com/fluxcd/source-controller/api/v1" - extgogit "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" -) - -const ( - aksTerraformPath = "./terraform/aks" - azureDevOpsKnownHosts = "ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H" -) - -type config struct { - kubeconfigPath string - kubeClient client.Client - - azdoPat string - idRsa string - idRsaPub string - knownHosts string - fleetInfraRepository repoConfig - applicationRepository repoConfig - - fluxAzureSp spConfig - sopsId string - acr acrConfig - eventHubSas string -} - -type spConfig struct { - tenantId string - clientId string - clientSecret string -} - -type repoConfig struct { - http string - ssh string -} - -type acrConfig struct { - url string - username string - password string -} - -var cfg config - -func TestMain(m *testing.M) { - exitVal, err := setup(m) - if err != nil { - log.Printf("Received an error while running setup: %v", err) - os.Exit(1) - } - os.Exit(exitVal) -} - -func setup(m *testing.M) (exitVal int, err error) { - ctx := context.TODO() - runtimeLog.SetLogger(klogr.New()) - // Setup Terraform binary and init state - log.Println("Setting up Azure test infrastructure") - i := install.NewInstaller() - // Find Terraform binary path - execPath, err := i.Ensure(ctx, []src.Source{ - &fs.AnyVersion{Product: &product.Terraform}, - }) - if err != nil { - return 0, fmt.Errorf("terraform exec path not found: %v", err) - } - tf, err := tfexec.NewTerraform(aksTerraformPath, execPath) - if err != nil { - return 0, fmt.Errorf("could not create terraform instance: %v", err) - } - log.Println("Init Terraform") - err = tf.Init(ctx, tfexec.Upgrade(true)) - if err != nil { - return 0, fmt.Errorf("error running init: %v", err) - } - - // Always destroy the infrastructure before exiting - defer func() { - log.Println("Tearing down Azure test infrastructure") - if ferr := tf.Destroy(ctx); ferr != nil { - err = multierr.Append(fmt.Errorf("could not destroy Azure infrastructure: %v", ferr), err) - } - }() - - // Check that we are starting from a clean state - log.Println("Checking for an empty Terraform state") - state, err := tf.Show(ctx) - if err != nil { - return 0, fmt.Errorf("could not read state: %v", err) - } - if state.Values != nil { - return 0, fmt.Errorf("expected an empty state but got existing resources") - } - - // Apply Terraform and read the output values - log.Println("Applying Terraform") - err = tf.Apply(ctx) - if err != nil { - return 0, fmt.Errorf("error running apply: %v", err) - } - state, err = tf.Show(ctx) - if err != nil { - return 0, fmt.Errorf("could not read state: %v", err) - } - outputs := state.Values.Outputs - kubeconfig := outputs["aks_kube_config"].Value.(string) - aksHost := outputs["aks_host"].Value.(string) - aksCert := outputs["aks_client_certificate"].Value.(string) - aksKey := outputs["aks_client_key"].Value.(string) - aksCa := outputs["aks_cluster_ca_certificate"].Value.(string) - azdoPat := outputs["shared_pat"].Value.(string) - idRsa := outputs["shared_id_rsa"].Value.(string) - idRsaPub := outputs["shared_id_rsa_pub"].Value.(string) - fleetInfraRepository := outputs["fleet_infra_repository"].Value.(map[string]interface{}) - applicationRepository := outputs["application_repository"].Value.(map[string]interface{}) - fluxAzureSp := outputs["flux_azure_sp"].Value.(map[string]interface{}) - sharedSopsId := outputs["sops_id"].Value.(string) - acr := outputs["acr"].Value.(map[string]interface{}) - eventHubSas := outputs["event_hub_sas"].Value.(string) - - // Setup Kubernetes clients for test cluster - log.Println("Creating Kubernetes client") - kubeconfigPath, kubeClient, err := getKubernetesCredentials(kubeconfig, aksHost, aksCert, aksKey, aksCa) - if err != nil { - return 0, fmt.Errorf("error create Kubernetes client: %v", err) - } - defer func() { - if ferr := os.RemoveAll(filepath.Dir(kubeconfigPath)); ferr != nil { - err = multierr.Append(fmt.Errorf("could not clean up kubeconfig file: %v", ferr), err) - } - }() - - // Install Flux in the new cluster - cfg = config{ - kubeconfigPath: kubeconfigPath, - kubeClient: kubeClient, - azdoPat: azdoPat, - idRsa: idRsa, - idRsaPub: idRsaPub, - knownHosts: azureDevOpsKnownHosts, - fleetInfraRepository: repoConfig{ - http: fleetInfraRepository["http"].(string), - ssh: fleetInfraRepository["ssh"].(string), - }, - applicationRepository: repoConfig{ - http: applicationRepository["http"].(string), - ssh: applicationRepository["ssh"].(string), - }, - fluxAzureSp: spConfig{ - tenantId: fluxAzureSp["tenant_id"].(string), - clientId: fluxAzureSp["client_id"].(string), - clientSecret: fluxAzureSp["client_secret"].(string), - }, - sopsId: sharedSopsId, - acr: acrConfig{ - url: acr["url"].(string), - username: acr["username"].(string), - password: acr["password"].(string), - }, - eventHubSas: eventHubSas, - } - err = installFlux(ctx, kubeClient, kubeconfigPath, cfg.fleetInfraRepository.http, azdoPat, cfg.fluxAzureSp) - if err != nil { - return 0, fmt.Errorf("error installing Flux: %v", err) - } - - // Run tests - log.Println("Running Azure e2e tests") - result := m.Run() - return result, nil -} - -func TestFluxInstallation(t *testing.T) { - ctx := context.TODO() - require.Eventually(t, func() bool { - err := verifyGitAndKustomization(ctx, cfg.kubeClient, "flux-system", "flux-system") - if err != nil { - return false - } - return true - }, 60*time.Second, 5*time.Second) -} - -func TestAzureDevOpsCloning(t *testing.T) { - ctx := context.TODO() - branchName := "feature/branch" - tagName := "v1" - - tests := []struct { - name string - refType string - cloneType string - }{ - { - name: "https-feature-branch", - refType: "branch", - cloneType: "http", - }, - { - name: "https-v1", - refType: "tag", - cloneType: "http", - }, - { - name: "ssh-feature-branch", - refType: "branch", - cloneType: "ssh", - }, - { - name: "ssh-v1", - refType: "tag", - cloneType: "ssh", - }, - } - - t.Log("Creating application sources") - repo, _, err := getRepository(cfg.applicationRepository.http, branchName, true, cfg.azdoPat) - require.NoError(t, err) - - files := make(map[string]io.Reader) - for _, tt := range tests { - manifest := fmt.Sprintf(` - apiVersion: v1 - kind: ConfigMap - metadata: - name: foobar - namespace: %s - `, tt.name) - name := fmt.Sprintf("cloning-test/%s/configmap.yaml", tt.name) - files[name] = strings.NewReader(manifest) - } - - err = commitAndPushAll(repo, files, branchName) - require.NoError(t, err) - err = createTagAndPush(repo, branchName, tagName, cfg.azdoPat) - require.NoError(t, err) - - t.Log("Verifying application-gitops namespaces") - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ref := &sourcev1.GitRepositoryRef{ - Branch: branchName, - } - if tt.refType == "tag" { - ref = &sourcev1.GitRepositoryRef{ - Tag: tagName, - } - } - url := cfg.applicationRepository.http - secretData := map[string]string{ - "username": "git", - "password": cfg.azdoPat, - } - if tt.cloneType == "ssh" { - url = cfg.applicationRepository.ssh - secretData = map[string]string{ - "identity": cfg.idRsa, - "identity.pub": cfg.idRsaPub, - "known_hosts": cfg.knownHosts, - } - } - - namespace := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: tt.name, - }, - } - _, err := controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &namespace, func() error { - return nil - }) - gitSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "git-credentials", - Namespace: namespace.Name, - }, - } - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, gitSecret, func() error { - gitSecret.StringData = secretData - return nil - }) - source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: tt.name, Namespace: namespace.Name}} - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, source, func() error { - source.Spec = sourcev1.GitRepositorySpec{ - Reference: ref, - SecretRef: &meta.LocalObjectReference{ - Name: gitSecret.Name, - }, - URL: url, - } - return nil - }) - require.NoError(t, err) - kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: tt.name, Namespace: namespace.Name}} - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error { - kustomization.Spec = kustomizev1.KustomizationSpec{ - Path: fmt.Sprintf("./cloning-test/%s", tt.name), - SourceRef: kustomizev1.CrossNamespaceSourceReference{ - Kind: sourcev1.GitRepositoryKind, - Name: tt.name, - Namespace: namespace.Name, - }, - Interval: metav1.Duration{Duration: 1 * time.Minute}, - Prune: true, - } - return nil - }) - require.NoError(t, err) - - // Wait for configmap to be deployed - require.Eventually(t, func() bool { - err := verifyGitAndKustomization(ctx, cfg.kubeClient, namespace.Name, tt.name) - if err != nil { - return false - } - nn := types.NamespacedName{Name: "foobar", Namespace: namespace.Name} - cm := &corev1.ConfigMap{} - err = cfg.kubeClient.Get(ctx, nn, cm) - if err != nil { - return false - } - return true - }, 120*time.Second, 5*time.Second) - }) - } -} - -func TestImageRepositoryACR(t *testing.T) { - ctx := context.TODO() - name := "image-repository-acr" - repoUrl := cfg.applicationRepository.http - oldVersion := "1.0.0" - newVersion := "1.0.1" - manifest := fmt.Sprintf(` - apiVersion: apps/v1 - kind: Deployment - metadata: - name: podinfo - namespace: %s - spec: - selector: - matchLabels: - app: podinfo - template: - metadata: - labels: - app: podinfo - spec: - containers: - - name: podinfod - image: %s/container/podinfo:%s # {"$imagepolicy": "%s:podinfo"} - readinessProbe: - exec: - command: - - podcli - - check - - http - - localhost:9898/readyz - initialDelaySeconds: 5 - timeoutSeconds: 5`, name, cfg.acr.url, oldVersion, name) - - repo, _, err := getRepository(repoUrl, name, true, cfg.azdoPat) - require.NoError(t, err) - files := make(map[string]io.Reader) - files["podinfo.yaml"] = strings.NewReader(manifest) - err = commitAndPushAll(repo, files, name) - require.NoError(t, err) - - err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name) - require.NoError(t, err) - acrSecret := corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "acr-docker", Namespace: name}} - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &acrSecret, func() error { - acrSecret.Type = corev1.SecretTypeDockerConfigJson - acrSecret.StringData = map[string]string{ - ".dockerconfigjson": fmt.Sprintf(` - { - "auths": { - "%s": { - "auth": "%s" - } - } - } - `, cfg.acr.url, b64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", cfg.acr.username, cfg.acr.password)))), - } - return nil - }) - require.NoError(t, err) - imageRepository := reflectorv1beta2.ImageRepository{ - ObjectMeta: metav1.ObjectMeta{ - Name: "podinfo", - Namespace: name, - }, - } - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &imageRepository, func() error { - imageRepository.Spec = reflectorv1beta2.ImageRepositorySpec{ - Image: fmt.Sprintf("%s/container/podinfo", cfg.acr.url), - Interval: metav1.Duration{ - Duration: 1 * time.Minute, - }, - SecretRef: &meta.LocalObjectReference{ - Name: acrSecret.Name, - }, - } - return nil - }) - require.NoError(t, err) - imagePolicy := reflectorv1beta2.ImagePolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "podinfo", - Namespace: name, - }, - } - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &imagePolicy, func() error { - imagePolicy.Spec = reflectorv1beta2.ImagePolicySpec{ - ImageRepositoryRef: meta.NamespacedObjectReference{ - Name: imageRepository.Name, - }, - Policy: reflectorv1beta2.ImagePolicyChoice{ - SemVer: &reflectorv1beta2.SemVerPolicy{ - Range: "1.0.x", - }, - }, - } - return nil - }) - require.NoError(t, err) - imageAutomation := automationv1beta1.ImageUpdateAutomation{ - ObjectMeta: metav1.ObjectMeta{ - Name: "podinfo", - Namespace: name, - }, - } - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &imageAutomation, func() error { - imageAutomation.Spec = automationv1beta1.ImageUpdateAutomationSpec{ - Interval: metav1.Duration{ - Duration: 1 * time.Minute, - }, - SourceRef: automationv1beta1.CrossNamespaceSourceReference{ - Kind: "GitRepository", - Name: name, - }, - GitSpec: &automationv1beta1.GitSpec{ - Checkout: &automationv1beta1.GitCheckoutSpec{ - Reference: sourcev1.GitRepositoryRef{ - Branch: name, - }, - }, - Commit: automationv1beta1.CommitSpec{ - Author: automationv1beta1.CommitUser{ - Email: "imageautomation@example.com", - Name: "imageautomation", - }, - }, - }, - } - return nil - }) - require.NoError(t, err) - - // Wait for image repository to be ready - require.Eventually(t, func() bool { - _, repoDir, err := getRepository(repoUrl, name, false, cfg.azdoPat) - if err != nil { - return false - } - b, err := os.ReadFile(filepath.Join(repoDir, "podinfo.yaml")) - if err != nil { - return false - } - if bytes.Contains(b, []byte(newVersion)) == false { - return false - } - return true - }, 120*time.Second, 5*time.Second) -} - -func TestKeyVaultSops(t *testing.T) { - ctx := context.TODO() - name := "key-vault-sops" - repoUrl := cfg.applicationRepository.http - secretYaml := `apiVersion: v1 -kind: Secret -metadata: - name: "test" - namespace: "key-vault-sops" -stringData: - foo: "bar"` - - repo, tmpDir, err := getRepository(repoUrl, name, true, cfg.azdoPat) - err = runCommand(ctx, 5*time.Minute, tmpDir, "mkdir -p ./key-vault-sops") - require.NoError(t, err) - err = runCommand(ctx, 5*time.Minute, tmpDir, fmt.Sprintf("echo \"%s\" > ./key-vault-sops/secret.enc.yaml", secretYaml)) - require.NoError(t, err) - err = runCommand(ctx, 5*time.Minute, tmpDir, fmt.Sprintf("sops --encrypt --encrypted-regex '^(data|stringData)$' --azure-kv %s --in-place ./key-vault-sops/secret.enc.yaml", cfg.sopsId)) - require.NoError(t, err) - - r, err := os.Open(fmt.Sprintf("%s/key-vault-sops/secret.enc.yaml", tmpDir)) - require.NoError(t, err) - - files := make(map[string]io.Reader) - files["key-vault-sops/secret.enc.yaml"] = r - err = commitAndPushAll(repo, files, name) - require.NoError(t, err) - - err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name) - require.NoError(t, err) - - source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}} - require.Eventually(t, func() bool { - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, source, func() error { - source.Spec = sourcev1.GitRepositorySpec{ - Reference: &sourcev1.GitRepositoryRef{ - Branch: name, - }, - SecretRef: &meta.LocalObjectReference{ - Name: "https-credentials", - }, - URL: repoUrl, - } - return nil - }) - if err != nil { - return false - } - return true - }, 10*time.Second, 1*time.Second) - kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}} - require.Eventually(t, func() bool { - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error { - kustomization.Spec = kustomizev1.KustomizationSpec{ - Path: "./key-vault-sops", - SourceRef: kustomizev1.CrossNamespaceSourceReference{ - Kind: sourcev1.GitRepositoryKind, - Name: source.Name, - Namespace: source.Namespace, - }, - Interval: metav1.Duration{Duration: 1 * time.Minute}, - Prune: true, - Decryption: &kustomizev1.Decryption{ - Provider: "sops", - }, - } - return nil - }) - if err != nil { - return false - } - return true - }, 10*time.Second, 1*time.Second) - - require.Eventually(t, func() bool { - nn := types.NamespacedName{Name: "test", Namespace: name} - secret := &corev1.Secret{} - err = cfg.kubeClient.Get(ctx, nn, secret) - if err != nil { - return false - } - return true - }, 120*time.Second, 5*time.Second) -} - -func TestAzureDevOpsCommitStatus(t *testing.T) { - ctx := context.TODO() - name := "commit-status" - repoUrl := cfg.applicationRepository.http - manifest := fmt.Sprintf(` - apiVersion: v1 - kind: ConfigMap - metadata: - name: foobar - namespace: %s - `, name) - - c, _, err := getRepository(repoUrl, name, true, cfg.azdoPat) - require.NoError(t, err) - - files := make(map[string]io.Reader) - files["configmap.yaml"] = strings.NewReader(manifest) - - err = commitAndPushAll(c, files, name) - require.NoError(t, err) - - err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name) - require.NoError(t, err) - - kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}} - require.Eventually(t, func() bool { - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error { - kustomization.Spec.HealthChecks = []meta.NamespacedObjectKindReference{ - { - APIVersion: "v1", - Kind: "ConfigMap", - Name: "foobar", - Namespace: name, - }, - } - return nil - }) - if err != nil { - return false - } - return true - }, 10*time.Second, 1*time.Second) - - require.Eventually(t, func() bool { - err := verifyGitAndKustomization(ctx, cfg.kubeClient, name, name) - if err != nil { - return false - } - return true - }, 10*time.Second, 1*time.Second) - - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "azuredevops-token", - Namespace: name, - }, - } - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &secret, func() error { - secret.StringData = map[string]string{ - "token": cfg.azdoPat, - } - return nil - }) - provider := notiv1beta2.Provider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "azuredevops", - Namespace: name, - }, - } - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &provider, func() error { - provider.Spec = notiv1beta2.ProviderSpec{ - Type: "azuredevops", - Address: repoUrl, - SecretRef: &meta.LocalObjectReference{ - Name: "azuredevops-token", - }, - } - return nil - }) - require.NoError(t, err) - alert := notiv1beta2.Alert{ - ObjectMeta: metav1.ObjectMeta{ - Name: "azuredevops", - Namespace: name, - }, - } - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &alert, func() error { - alert.Spec = notiv1beta2.AlertSpec{ - ProviderRef: meta.LocalObjectReference{ - Name: provider.Name, - }, - EventSources: []notiv1.CrossNamespaceObjectReference{ - { - Kind: "Kustomization", - Name: name, - Namespace: name, - }, - }, - } - return nil - }) - require.NoError(t, err) - - u, err := giturls.Parse(repoUrl) - require.NoError(t, err) - id := strings.TrimLeft(u.Path, "/") - id = strings.TrimSuffix(id, ".git") - comp := strings.Split(id, "/") - orgUrl := fmt.Sprintf("%s://%s/%v", u.Scheme, u.Host, comp[0]) - project := comp[1] - repoId := comp[3] - - repo, err := extgogit.PlainOpen(c.Path()) - require.NoError(t, err) - - ref, err := repo.Reference(plumbing.NewBranchReferenceName(name), false) - require.NoError(t, err) - - rev := ref.Hash().String() - connection := azuredevops.NewPatConnection(orgUrl, cfg.azdoPat) - client, err := git.NewClient(ctx, connection) - require.NoError(t, err) - getArgs := git.GetStatusesArgs{ - Project: &project, - RepositoryId: &repoId, - CommitId: &rev, - } - require.Eventually(t, func() bool { - statuses, err := client.GetStatuses(ctx, getArgs) - if err != nil { - return false - } - if len(*statuses) != 1 { - return false - } - return true - }, 500*time.Second, 5*time.Second) -} - -func TestEventHubNotification(t *testing.T) { - ctx := context.TODO() - name := "event-hub" - - // Start listening to eventhub with latest offset - hub, err := eventhub.NewHubFromConnectionString(cfg.eventHubSas) - require.NoError(t, err) - c := make(chan string, 10) - handler := func(ctx context.Context, event *eventhub.Event) error { - c <- string(event.Data) - return nil - } - runtimeInfo, err := hub.GetRuntimeInformation(ctx) - require.NoError(t, err) - require.Equal(t, 1, len(runtimeInfo.PartitionIDs)) - listenerHandler, err := hub.Receive(ctx, runtimeInfo.PartitionIDs[0], handler, eventhub.ReceiveWithLatestOffset()) - require.NoError(t, err) - - // Setup Flux resources - repoUrl := cfg.applicationRepository.http - manifest := fmt.Sprintf(` - apiVersion: v1 - kind: ConfigMap - metadata: - name: foobar - namespace: %s - `, name) - - repo, repoDir, err := getRepository(repoUrl, name, true, cfg.azdoPat) - require.NoError(t, err) - err = addFile(repoDir, "configmap.yaml", manifest) - files := make(map[string]io.Reader) - files["configmap.yaml"] = strings.NewReader(manifest) - require.NoError(t, err) - err = commitAndPushAll(repo, files, name) - require.NoError(t, err) - - err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name) - require.NoError(t, err) - - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: name, - }, - } - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &secret, func() error { - secret.StringData = map[string]string{ - "address": cfg.eventHubSas, - } - return nil - }) - provider := notiv1beta2.Provider{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: name, - }, - } - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &provider, func() error { - provider.Spec = notiv1beta2.ProviderSpec{ - Type: "azureeventhub", - Address: repoUrl, - SecretRef: &meta.LocalObjectReference{ - Name: name, - }, - } - return nil - }) - require.NoError(t, err) - alert := notiv1beta2.Alert{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: name, - }, - } - _, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &alert, func() error { - alert.Spec = notiv1beta2.AlertSpec{ - ProviderRef: meta.LocalObjectReference{ - Name: provider.Name, - }, - EventSources: []notiv1.CrossNamespaceObjectReference{ - { - Kind: "Kustomization", - Name: name, - Namespace: name, - }, - }, - Summary: "cluster: test-1", - } - return nil - }) - require.NoError(t, err) - - kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}} - require.Eventually(t, func() bool { - _, err := controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error { - kustomization.Spec.HealthChecks = []meta.NamespacedObjectKindReference{ - { - APIVersion: "v1", - Kind: "ConfigMap", - Name: "foobar", - Namespace: name, - }, - } - return nil - }) - if err != nil { - return false - } - return true - }, 10*time.Second, 1*time.Second) - - require.Eventually(t, func() bool { - err := verifyGitAndKustomization(ctx, cfg.kubeClient, name, name) - if err != nil { - return false - } - return true - }, 60*time.Second, 5*time.Second) - - // Wait to read even from event hub - require.Eventually(t, func() bool { - select { - case eventJson := <-c: - event := &eventv1.Event{} - err := json.Unmarshal([]byte(eventJson), event) - if err != nil { - t.Logf("the received event type does not match Flux format, error: %v", err) - return false - } - - if event.InvolvedObject.Kind == kustomizev1.KustomizationKind && - strings.Contains(event.Message, "Health check passed") { - return true - } - - t.Logf("event received from '%s/%s': %s", - event.InvolvedObject.Kind, event.InvolvedObject.Name, event.Message) - return false - default: - return false - } - }, 60*time.Second, 1*time.Second) - err = listenerHandler.Close(ctx) - require.NoError(t, err) - err = hub.Close(ctx) - require.NoError(t, err) -} - -// TODO: Enable when source-controller supports Helm charts from OCI sources. -/*func TestACRHelmRelease(t *testing.T) { - ctx := context.TODO() - - // Create namespace for test - namespace := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "acr-helm-release", - }, - } - err := kubeClient.Create(ctx, &namespace) - require.NoError(t, err) - defer func() { - kubeClient.Delete(ctx, &namespace) - require.NoError(t, err) - }() - - // Copy ACR credentials to new namespace - acrNn := types.NamespacedName{ - Name: "acr-helm", - Namespace: "flux-system", - } - acrSecret := corev1.Secret{} - err = kubeClient.Get(ctx, acrNn, &acrSecret) - require.NoError(t, err) - acrSecret.ObjectMeta = metav1.ObjectMeta{ - Name: acrSecret.Name, - Namespace: namespace.Name, - } - err = kubeClient.Create(ctx, &acrSecret) - require.NoError(t, err) - - // Create HelmRepository and wait for it to sync - helmRepository := sourcev1.HelmRepository{ - ObjectMeta: metav1.ObjectMeta{ - Name: "acr", - Namespace: namespace.Name, - }, - Spec: sourcev1.HelmRepositorySpec{ - URL: "https://acrappsoarfish.azurecr.io/helm/podinfo", - Interval: metav1.Duration{ - Duration: 5 * time.Minute, - }, - SecretRef: &meta.LocalObjectReference{ - Name: acrSecret.Name, - }, - PassCredentials: true, - }, - } - err = kubeClient.Create(ctx, &helmRepository) - require.NoError(t, err) -}*/ diff --git a/tests/azure/go.mod b/tests/azure/go.mod deleted file mode 100644 index d46f209fcf..0000000000 --- a/tests/azure/go.mod +++ /dev/null @@ -1,123 +0,0 @@ -module github.com/fluxcd/flux2/tests/azure - -go 1.20 - -// Replace go-git to unreleased v5.8.0 to improve support for Git v2.41+. -replace github.com/go-git/go-git/v5 => github.com/go-git/go-git/v5 v5.7.1-0.20230702134234-dd4e2b7f4b01 - -require ( - github.com/Azure/azure-event-hubs-go/v3 v3.6.0 - github.com/fluxcd/helm-controller/api v0.35.0 - github.com/fluxcd/image-automation-controller/api v0.35.0 - github.com/fluxcd/image-reflector-controller/api v0.29.1 - github.com/fluxcd/kustomize-controller/api v1.0.1 - github.com/fluxcd/notification-controller/api v1.0.0 - github.com/fluxcd/pkg/apis/event v0.5.1 - github.com/fluxcd/pkg/apis/meta v1.1.1 - github.com/fluxcd/pkg/git v0.12.3 - github.com/fluxcd/pkg/git/gogit v0.12.1 - github.com/fluxcd/source-controller/api v1.0.1 - github.com/go-git/go-git/v5 v5.7.0 - github.com/hashicorp/hc-install v0.5.2 - github.com/hashicorp/terraform-exec v0.18.1 - github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 - github.com/stretchr/testify v1.8.4 - github.com/whilp/git-urls v1.0.0 - go.uber.org/multierr v1.11.0 - k8s.io/api v0.27.3 - k8s.io/apimachinery v0.27.3 - k8s.io/client-go v0.27.3 - k8s.io/klog/v2 v2.100.1 - sigs.k8s.io/controller-runtime v0.15.0 -) - -// Fix CVE-2022-28948 -replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1 - -require ( - github.com/Azure/azure-amqp-common-go/v4 v4.2.0 // indirect - github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect - github.com/Azure/go-amqp v1.0.0 // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.28 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect - github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect - github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect - github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect - github.com/Azure/go-autorest/logger v0.2.1 // indirect - github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/Masterminds/semver/v3 v3.2.1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230619160724-3fbb1f12458c // indirect - github.com/acomagu/bufpipe v1.0.4 // indirect - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect - github.com/cloudflare/circl v1.3.3 // indirect - github.com/cyphar/filepath-securejoin v0.2.3 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/devigned/tab v0.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.10.0 // indirect - github.com/emirpasic/gods v1.18.1 // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect - github.com/fluxcd/pkg/apis/kustomize v1.1.1 // indirect - github.com/fluxcd/pkg/ssh v0.8.0 // indirect - github.com/fluxcd/pkg/version v0.2.2 // indirect - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.4.1 // indirect - github.com/go-logr/logr v1.2.4 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.1 // indirect - github.com/go-openapi/swag v0.22.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.4.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/gnostic v0.6.9 // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/terraform-json v0.15.0 // indirect - github.com/imdario/mergo v0.3.15 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/jpillora/backoff v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sergi/go-diff v1.3.1 // indirect - github.com/skeema/knownhosts v1.1.1 // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/zclconf/go-cty v1.13.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - golang.org/x/crypto v0.10.0 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/oauth2 v0.5.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/term v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.9.1 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.30.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.27.3 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect -) diff --git a/tests/azure/terraform/aks/.terraform.lock.hcl b/tests/azure/terraform/aks/.terraform.lock.hcl deleted file mode 100644 index cc3c784612..0000000000 --- a/tests/azure/terraform/aks/.terraform.lock.hcl +++ /dev/null @@ -1,81 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/azuread" { - version = "2.28.0" - constraints = "2.28.0" - hashes = [ - "h1:22zcPLrP6T0FAGzhkx44Oc3SreGpzttng34JSYhoknE=", - "zh:0e8b008417d74f7d7f931effe48c0719f20789440c9c5932c2b1cf4110348f41", - "zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7", - "zh:2a2e4408fc1dc902553ff6a5751924c5e9a59df30f0668b55aa6c07264537c03", - "zh:2ab09b735888a7402bdd8e74f75a053ac102e0a01b876b0608a0c240dff57b2e", - "zh:2ac1f45bb1597726ff6822e1f9a7bc7227179c10b0b51533849b44ab278a05ed", - "zh:601a7821c7fbef870a1a2165a684e4fb4f4c84f6b85e0ce51ef7783a581cf594", - "zh:7f8e4dd03a3d4259e06b498ed0b04c6911aa99cf5f01018e2092899cd135c6e5", - "zh:8408143a24baaf4ad527aeecfaf11dfcd0fb6f25648958f2c94464717f776206", - "zh:bc836c1389f7b01537eb71ec709ea9d1cb4180814b70992ce3004356ce28d173", - "zh:d4b5571c96c2bafdf79494265f508dbe569f6fb16a5ddc41f22da22e9be029e9", - "zh:f1c2a1a13fe3725ba84b57a418adb1bd8c93db09dd880658a468cbd4832f9224", - "zh:f39b090d45674395fecb39add1260dd4565661e38eb40c4017c3fd84c8af1717", - ] -} - -provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.20.0" - constraints = "3.20.0" - hashes = [ - "h1:heH/4bYgajEFQ+fwSV9Zduvpyb7eTCQUv+gl201EFg8=", - "zh:0d534bb2fed67b5b58d3adb2b0be7a9986f62b34f40eae450dafc9454fb54db8", - "zh:19f6d5f196a35500e0f1ae9d9baee44f49b90858524338a7b8aaec06d3e3a047", - "zh:1d042648d2eaffde8858a8006b944374599c5e8c2f834ae74b97adedd1468142", - "zh:278ebac38cf3c1e6df4bc5de00e931bfc04298607f428aa84a932bbf26dee421", - "zh:48f29b802e2de7e6dd2452a012c633686fce5d7ad3eadb490a7b8c0967a9ebfa", - "zh:731bf2e97c4a519723682beb2e85e065bf0bf53b2f50e2ff7b15b39ea74e37ff", - "zh:7c8187ebca19ca8f6ef82d3d79a418ccfa6574bb99e63cc930fa46ff938a7921", - "zh:82fdb2052601f6fa925195e77506fb609ce8bb4a6f6e94cf6a5058252ef570d4", - "zh:995ca23bb3765a16c6b3138b468d920acff5742b22492324c836579e3344ea40", - "zh:a970131232ad41203382f6fa3f0014a22767cbfe28cd7562346184ea6e678d63", - "zh:bf5036675a7f0b8691fe393e2782a76c7943ba17eec7255e16a31c7547436a48", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} - -provider "registry.terraform.io/hashicorp/random" { - version = "3.4.3" - hashes = [ - "h1:xZGZf18JjMS06pFa4NErzANI98qi59SEcBsOcS2P2yQ=", - "zh:41c53ba47085d8261590990f8633c8906696fa0a3c4b384ff6a7ecbf84339752", - "zh:59d98081c4475f2ad77d881c4412c5129c56214892f490adf11c7e7a5a47de9b", - "zh:686ad1ee40b812b9e016317e7f34c0d63ef837e084dea4a1f578f64a6314ad53", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:84103eae7251384c0d995f5a257c72b0096605048f757b749b7b62107a5dccb3", - "zh:8ee974b110adb78c7cd18aae82b2729e5124d8f115d484215fd5199451053de5", - "zh:9dd4561e3c847e45de603f17fa0c01ae14cae8c4b7b4e6423c9ef3904b308dda", - "zh:bb07bb3c2c0296beba0beec629ebc6474c70732387477a65966483b5efabdbc6", - "zh:e891339e96c9e5a888727b45b2e1bb3fcbdfe0fd7c5b4396e4695459b38c8cb1", - "zh:ea4739860c24dfeaac6c100b2a2e357106a89d18751f7693f3c31ecf6a996f8d", - "zh:f0c76ac303fd0ab59146c39bc121c5d7d86f878e9a69294e29444d4c653786f8", - "zh:f143a9a5af42b38fed328a161279906759ff39ac428ebcfe55606e05e1518b93", - ] -} - -provider "registry.terraform.io/microsoft/azuredevops" { - version = "0.2.2" - constraints = "0.2.2" - hashes = [ - "h1:oKfPQ5Tp9WNeacY08gMifP3G9I//o3LW6qTLsveJwi0=", - "zh:016142d26ec662949ba95b6c84672b243b54bbdca04cf8714fe0b4318783a72d", - "zh:0337b3c4e023bb56b23a5d2d9abe917f197eed378fa69803e9d0b11a36211e15", - "zh:240c9636660292eeb99bd892602eafe2e5c22b469b082de6963e31dab9e0092e", - "zh:439151590a489a7c0cde50ee701fdbf254e67bdbeaa2acd2a99d005c4051d518", - "zh:6086f5eab87662678eef7bc83041eab5667e92189eb3089b966aeb2cdb58d299", - "zh:94a64223905bb3cef2c38e163ae56ef841422e6511a79f8e60272edd7f8fc67f", - "zh:9d9545445607c5ba6482da0137464d5de4c3459ae1671e6ff94e337e5943c0eb", - "zh:a53bfdea73985ed31acbadd200b295745662a4a54e8c37f050faf71dab7deb8b", - "zh:aa6943db7093b2556fcc2ee5b8b5a8a48e625ded2b063183fbc5a52c94d133f2", - "zh:af4729e8fe8ec255e4c4ca0e6dd4cf43d855bfe4c45b2aa6e47d8c35be55813d", - "zh:bdf8752a6cd12ba3a33597bf7519825000a498b655c72be8c8df504bb9f70fe5", - "zh:c760fa7bc5c62d56c54ef41b4b03b0ae391149f46f36c8c8a55d2511e7f8e599", - ] -} diff --git a/tests/azure/terraform/aks/aks.tf b/tests/azure/terraform/aks/aks.tf deleted file mode 100644 index aab1a0cfcb..0000000000 --- a/tests/azure/terraform/aks/aks.tf +++ /dev/null @@ -1,35 +0,0 @@ -resource "azurerm_kubernetes_cluster" "this" { - name = "aks-${local.name_suffix}" - location = azurerm_resource_group.this.location - resource_group_name = azurerm_resource_group.this.name - - dns_prefix = "aks${local.name_suffix}" - - default_node_pool { - name = "default" - node_count = 2 - vm_size = "Standard_B2s" - os_disk_size_gb = 30 - } - - identity { - type = "SystemAssigned" - } - - role_based_access_control_enabled = true - - network_profile { - network_plugin = "kubenet" - network_policy = "calico" - } - - tags = { - environment = "e2e" - } -} - -resource "azurerm_role_assignment" "aks_acr_pull" { - scope = data.azurerm_container_registry.shared.id - role_definition_name = "AcrPull" - principal_id = azurerm_kubernetes_cluster.this.kubelet_identity[0].object_id -} diff --git a/tests/azure/terraform/aks/azuredevops.tf b/tests/azure/terraform/aks/azuredevops.tf deleted file mode 100644 index dd1ebc501a..0000000000 --- a/tests/azure/terraform/aks/azuredevops.tf +++ /dev/null @@ -1,21 +0,0 @@ -data "azuredevops_project" "e2e" { - name = "e2e" -} - -resource "azuredevops_git_repository" "fleet_infra" { - project_id = data.azuredevops_project.e2e.id - name = "fleet-infra-${local.name_suffix}" - default_branch = "refs/heads/main" - initialization { - init_type = "Clean" - } -} - -resource "azuredevops_git_repository" "application" { - project_id = data.azuredevops_project.e2e.id - name = "application-${local.name_suffix}" - default_branch = "refs/heads/main" - initialization { - init_type = "Clean" - } -} diff --git a/tests/azure/terraform/aks/keyvault.tf b/tests/azure/terraform/aks/keyvault.tf deleted file mode 100644 index 9133306a85..0000000000 --- a/tests/azure/terraform/aks/keyvault.tf +++ /dev/null @@ -1,37 +0,0 @@ -resource "azurerm_key_vault" "this" { - name = "kv-${random_pet.suffix.id}" - resource_group_name = azurerm_resource_group.this.name - location = azurerm_resource_group.this.location - tenant_id = data.azurerm_client_config.current.tenant_id - sku_name = "standard" -} - -resource "azurerm_key_vault_access_policy" "sops_write" { - key_vault_id = azurerm_key_vault.this.id - tenant_id = data.azurerm_client_config.current.tenant_id - object_id = data.azurerm_client_config.current.object_id - - key_permissions = [ - "Encrypt", - "Decrypt", - "Create", - "Delete", - "Purge", - "Get", - "List", - ] -} - -resource "azurerm_key_vault_key" "sops" { - depends_on = [azurerm_key_vault_access_policy.sops_write] - - name = "sops" - key_vault_id = azurerm_key_vault.this.id - key_type = "RSA" - key_size = 2048 - - key_opts = [ - "decrypt", - "encrypt", - ] -} diff --git a/tests/azure/terraform/aks/main.tf b/tests/azure/terraform/aks/main.tf deleted file mode 100644 index 0e70f4a1cd..0000000000 --- a/tests/azure/terraform/aks/main.tf +++ /dev/null @@ -1,52 +0,0 @@ -terraform { - backend "azurerm" { - resource_group_name = "terraform-state" - storage_account_name = "terraformstate0419" - container_name = "aks-tfstate" - key = "prod.terraform.tfstate" - } - - required_version = "1.2.8" - - required_providers { - azurerm = { - source = "hashicorp/azurerm" - version = "3.20.0" - } - azuread = { - source = "hashicorp/azuread" - version = "2.28.0" - } - azuredevops = { - source = "microsoft/azuredevops" - version = "0.2.2" - } - } -} - -provider "azurerm" { - features {} -} - -provider "azuredevops" { - org_service_url = "https://dev.azure.com/${local.azure_devops_org}" - personal_access_token = data.azurerm_key_vault_secret.shared_pat.value -} - -data "azurerm_client_config" "current" {} - -resource "random_pet" "suffix" {} - -locals { - azure_devops_org = "flux-azure" - name_suffix = "e2e-${random_pet.suffix.id}" -} - -resource "azurerm_resource_group" "this" { - name = "rg-${local.name_suffix}" - location = "West Europe" - - tags = { - environment = "e2e" - } -} diff --git a/tests/azure/terraform/aks/outputs.tf b/tests/azure/terraform/aks/outputs.tf deleted file mode 100644 index 21a8f1de3d..0000000000 --- a/tests/azure/terraform/aks/outputs.tf +++ /dev/null @@ -1,80 +0,0 @@ -output "aks_kube_config" { - sensitive = true - value = azurerm_kubernetes_cluster.this.kube_config_raw -} - -output "aks_host" { - value = azurerm_kubernetes_cluster.this.kube_config[0].host - sensitive = true -} - -output "aks_client_certificate" { - value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].client_certificate) - sensitive = true -} - -output "aks_client_key" { - value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].client_key) - sensitive = true -} - -output "aks_cluster_ca_certificate" { - value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].cluster_ca_certificate) - sensitive = true -} - -output "shared_pat" { - sensitive = true - value = data.azurerm_key_vault_secret.shared_pat.value -} - -output "shared_id_rsa" { - sensitive = true - value = data.azurerm_key_vault_secret.shared_id_rsa.value -} - -output "shared_id_rsa_pub" { - sensitive = true - value = data.azurerm_key_vault_secret.shared_id_rsa_pub.value -} - -output "fleet_infra_repository" { - value = { - http = azuredevops_git_repository.fleet_infra.remote_url - ssh = "ssh://git@ssh.dev.azure.com/v3/${local.azure_devops_org}/${azuredevops_git_repository.fleet_infra.project_id}/${azuredevops_git_repository.fleet_infra.name}" - } -} - -output "application_repository" { - value = { - http = azuredevops_git_repository.application.remote_url - ssh = "ssh://git@ssh.dev.azure.com/v3/${local.azure_devops_org}/${azuredevops_git_repository.application.project_id}/${azuredevops_git_repository.application.name}" - } -} - -output "flux_azure_sp" { - value = { - tenant_id = data.azurerm_client_config.current.tenant_id - client_id = azuread_service_principal.flux.application_id - client_secret = azuread_service_principal_password.flux.value - } - sensitive = true -} - -output "event_hub_sas" { - value = azurerm_eventhub_authorization_rule.this.primary_connection_string - sensitive = true -} - -output "sops_id" { - value = azurerm_key_vault_key.sops.id -} - -output "acr" { - value = { - url = data.azurerm_container_registry.shared.login_server - username = azuread_service_principal.flux.application_id - password = azuread_service_principal_password.flux.value - } - sensitive = true -} diff --git a/tests/azure/terraform/aks/service-principal.tf b/tests/azure/terraform/aks/service-principal.tf deleted file mode 100644 index db46718b15..0000000000 --- a/tests/azure/terraform/aks/service-principal.tf +++ /dev/null @@ -1,52 +0,0 @@ -resource "azuread_application" "flux" { - display_name = "flux-${local.name_suffix}" - - required_resource_access { - resource_app_id = "00000003-0000-0000-c000-000000000000" - - resource_access { - id = "df021288-bdef-4463-88db-98f22de89214" - type = "Role" - } - } - - required_resource_access { - resource_app_id = "00000002-0000-0000-c000-000000000000" - - resource_access { - id = "1cda74f2-2616-4834-b122-5cb1b07f8a59" - type = "Role" - } - resource_access { - id = "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175" - type = "Role" - } - } -} - -resource "azuread_service_principal" "flux" { - application_id = azuread_application.flux.application_id -} - -resource "azuread_service_principal_password" "flux" { - service_principal_id = azuread_service_principal.flux.object_id -} - -resource "azurerm_role_assignment" "acr" { - scope = data.azurerm_container_registry.shared.id - role_definition_name = "AcrPull" - principal_id = azuread_service_principal.flux.object_id -} - -resource "azurerm_key_vault_access_policy" "sops_decrypt" { - key_vault_id = azurerm_key_vault.this.id - tenant_id = data.azurerm_client_config.current.tenant_id - object_id = azuread_service_principal.flux.object_id - - key_permissions = [ - "Encrypt", - "Decrypt", - "Get", - "List", - ] -} diff --git a/tests/azure/terraform/aks/shared.tf b/tests/azure/terraform/aks/shared.tf deleted file mode 100644 index 30fccf5f59..0000000000 --- a/tests/azure/terraform/aks/shared.tf +++ /dev/null @@ -1,32 +0,0 @@ -locals { - shared_suffix = "oarfish" -} - -data "azurerm_resource_group" "shared" { - name = "e2e-shared" -} - -data "azurerm_container_registry" "shared" { - name = "acrapps${local.shared_suffix}" - resource_group_name = data.azurerm_resource_group.shared.name -} - -data "azurerm_key_vault" "shared" { - resource_group_name = data.azurerm_resource_group.shared.name - name = "kv-credentials-${local.shared_suffix}" -} - -data "azurerm_key_vault_secret" "shared_pat" { - key_vault_id = data.azurerm_key_vault.shared.id - name = "pat" -} - -data "azurerm_key_vault_secret" "shared_id_rsa" { - key_vault_id = data.azurerm_key_vault.shared.id - name = "id-rsa" -} - -data "azurerm_key_vault_secret" "shared_id_rsa_pub" { - key_vault_id = data.azurerm_key_vault.shared.id - name = "id-rsa-pub" -} diff --git a/tests/azure/terraform/shared/.terraform.lock.hcl b/tests/azure/terraform/shared/.terraform.lock.hcl deleted file mode 100644 index 03a5e2fe82..0000000000 --- a/tests/azure/terraform/shared/.terraform.lock.hcl +++ /dev/null @@ -1,61 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/azuread" { - version = "2.28.0" - constraints = "2.28.0" - hashes = [ - "h1:22zcPLrP6T0FAGzhkx44Oc3SreGpzttng34JSYhoknE=", - "zh:0e8b008417d74f7d7f931effe48c0719f20789440c9c5932c2b1cf4110348f41", - "zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7", - "zh:2a2e4408fc1dc902553ff6a5751924c5e9a59df30f0668b55aa6c07264537c03", - "zh:2ab09b735888a7402bdd8e74f75a053ac102e0a01b876b0608a0c240dff57b2e", - "zh:2ac1f45bb1597726ff6822e1f9a7bc7227179c10b0b51533849b44ab278a05ed", - "zh:601a7821c7fbef870a1a2165a684e4fb4f4c84f6b85e0ce51ef7783a581cf594", - "zh:7f8e4dd03a3d4259e06b498ed0b04c6911aa99cf5f01018e2092899cd135c6e5", - "zh:8408143a24baaf4ad527aeecfaf11dfcd0fb6f25648958f2c94464717f776206", - "zh:bc836c1389f7b01537eb71ec709ea9d1cb4180814b70992ce3004356ce28d173", - "zh:d4b5571c96c2bafdf79494265f508dbe569f6fb16a5ddc41f22da22e9be029e9", - "zh:f1c2a1a13fe3725ba84b57a418adb1bd8c93db09dd880658a468cbd4832f9224", - "zh:f39b090d45674395fecb39add1260dd4565661e38eb40c4017c3fd84c8af1717", - ] -} - -provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.20.0" - constraints = "3.20.0" - hashes = [ - "h1:heH/4bYgajEFQ+fwSV9Zduvpyb7eTCQUv+gl201EFg8=", - "zh:0d534bb2fed67b5b58d3adb2b0be7a9986f62b34f40eae450dafc9454fb54db8", - "zh:19f6d5f196a35500e0f1ae9d9baee44f49b90858524338a7b8aaec06d3e3a047", - "zh:1d042648d2eaffde8858a8006b944374599c5e8c2f834ae74b97adedd1468142", - "zh:278ebac38cf3c1e6df4bc5de00e931bfc04298607f428aa84a932bbf26dee421", - "zh:48f29b802e2de7e6dd2452a012c633686fce5d7ad3eadb490a7b8c0967a9ebfa", - "zh:731bf2e97c4a519723682beb2e85e065bf0bf53b2f50e2ff7b15b39ea74e37ff", - "zh:7c8187ebca19ca8f6ef82d3d79a418ccfa6574bb99e63cc930fa46ff938a7921", - "zh:82fdb2052601f6fa925195e77506fb609ce8bb4a6f6e94cf6a5058252ef570d4", - "zh:995ca23bb3765a16c6b3138b468d920acff5742b22492324c836579e3344ea40", - "zh:a970131232ad41203382f6fa3f0014a22767cbfe28cd7562346184ea6e678d63", - "zh:bf5036675a7f0b8691fe393e2782a76c7943ba17eec7255e16a31c7547436a48", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} - -provider "registry.terraform.io/hashicorp/random" { - version = "3.3.2" - hashes = [ - "h1:H5V+7iXol/EHB2+BUMzGlpIiCOdV74H8YjzCxnSAWcg=", - "zh:038293aebfede983e45ee55c328e3fde82ae2e5719c9bd233c324cfacc437f9c", - "zh:07eaeab03a723d83ac1cc218f3a59fceb7bbf301b38e89a26807d1c93c81cef8", - "zh:427611a4ce9d856b1c73bea986d841a969e4c2799c8ac7c18798d0cc42b78d32", - "zh:49718d2da653c06a70ba81fd055e2b99dfd52dcb86820a6aeea620df22cd3b30", - "zh:5574828d90b19ab762604c6306337e6cd430e65868e13ef6ddb4e25ddb9ad4c0", - "zh:7222e16f7833199dabf1bc5401c56d708ec052b2a5870988bc89ff85b68a5388", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:b1b2d7d934784d2aee98b0f8f07a8ccfc0410de63493ae2bf2222c165becf938", - "zh:b8f85b6a20bd264fcd0814866f415f0a368d1123cd7879c8ebbf905d370babc8", - "zh:c3813133acc02bbebddf046d9942e8ba5c35fc99191e3eb057957dafc2929912", - "zh:e7a41dbc919d1de800689a81c240c27eec6b9395564630764ebb323ea82ac8a9", - "zh:ee6d23208449a8eaa6c4f203e33f5176fa795b4b9ecf32903dffe6e2574732c2", - ] -} diff --git a/tests/azure/terraform/shared/acr.tf b/tests/azure/terraform/shared/acr.tf deleted file mode 100644 index a339feed52..0000000000 --- a/tests/azure/terraform/shared/acr.tf +++ /dev/null @@ -1,6 +0,0 @@ -resource "azurerm_container_registry" "this" { - name = "acrapps${random_pet.suffix.id}" - resource_group_name = azurerm_resource_group.this.name - location = azurerm_resource_group.this.location - sku = "Standard" -} diff --git a/tests/azure/terraform/shared/keyvault.tf b/tests/azure/terraform/shared/keyvault.tf deleted file mode 100644 index baf1eca030..0000000000 --- a/tests/azure/terraform/shared/keyvault.tf +++ /dev/null @@ -1,43 +0,0 @@ -resource "azurerm_key_vault" "this" { - name = "kv-credentials-${random_pet.suffix.id}" - resource_group_name = azurerm_resource_group.this.name - location = azurerm_resource_group.this.location - tenant_id = data.azurerm_client_config.current.tenant_id - sku_name = "standard" -} - -resource "azurerm_key_vault_access_policy" "admin" { - key_vault_id = azurerm_key_vault.this.id - tenant_id = data.azurerm_client_config.current.tenant_id - object_id = data.azurerm_client_config.current.object_id - - key_permissions = [ - "Backup", - "Create", - "Decrypt", - "Delete", - "Encrypt", - "Get", - "Import", - "List", - "Purge", - "Recover", - "Restore", - "Sign", - "UnwrapKey", - "Update", - "Verify", - "WrapKey", - ] - - secret_permissions = [ - "Backup", - "Delete", - "Get", - "List", - "Purge", - "Recover", - "Restore", - "Set", - ] -} diff --git a/tests/azure/terraform/shared/main.tf b/tests/azure/terraform/shared/main.tf deleted file mode 100644 index 36623f6b13..0000000000 --- a/tests/azure/terraform/shared/main.tf +++ /dev/null @@ -1,39 +0,0 @@ -terraform { - backend "azurerm" { - resource_group_name = "terraform-state" - storage_account_name = "terraformstate0419" - container_name = "shared-tfstate" - key = "prod.terraform.tfstate" - } - - required_version = "1.2.8" - - required_providers { - azurerm = { - source = "hashicorp/azurerm" - version = "3.20.0" - } - azuread = { - source = "hashicorp/azuread" - version = "2.28.0" - } - } -} - -provider "azurerm" { - features {} -} - -resource "random_pet" "suffix" { - length = 1 - separator = "" -} - -data "azurerm_client_config" "current" {} - -data "azurerm_subscription" "current" {} - -resource "azurerm_resource_group" "this" { - name = "e2e-shared" - location = "West Europe" -} diff --git a/tests/azure/terraform/shared/outputs.tf b/tests/azure/terraform/shared/outputs.tf deleted file mode 100644 index ff0e09f4b0..0000000000 --- a/tests/azure/terraform/shared/outputs.tf +++ /dev/null @@ -1,18 +0,0 @@ -output "azure_devops_sp" { - value = { - client_id = azuread_service_principal.azure_devops.application_id - client_secret = azuread_application_password.azure_devops.value - } - sensitive = true -} - -output "github_sp" { - value = { - tenant_id = data.azurerm_client_config.current.tenant_id - subscription_id = data.azurerm_client_config.current.subscription_id - client_id = azuread_service_principal.github.application_id - client_secret = azuread_application_password.github.value - } - sensitive = true -} - diff --git a/tests/azure/terraform/shared/service-principal.tf b/tests/azure/terraform/shared/service-principal.tf deleted file mode 100644 index eaed3a6e09..0000000000 --- a/tests/azure/terraform/shared/service-principal.tf +++ /dev/null @@ -1,105 +0,0 @@ -# Service Principal used by Azure DevOps to push OCI and Helm Charts -resource "azuread_application" "azure_devops" { - display_name = "azure-devops-${random_pet.suffix.id}" - - required_resource_access { - resource_app_id = "00000003-0000-0000-c000-000000000000" - - resource_access { - id = "df021288-bdef-4463-88db-98f22de89214" - type = "Role" - } - } - - required_resource_access { - resource_app_id = "00000002-0000-0000-c000-000000000000" - - resource_access { - id = "1cda74f2-2616-4834-b122-5cb1b07f8a59" - type = "Role" - } - resource_access { - id = "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175" - type = "Role" - } - } -} - -resource "azuread_application_password" "azure_devops" { - display_name = "password" - application_object_id = azuread_application.azure_devops.object_id -} - -resource "azuread_service_principal" "azure_devops" { - application_id = azuread_application.azure_devops.application_id -} - -resource "azurerm_role_assignment" "azure_devops_acr" { - scope = azurerm_container_registry.this.id - role_definition_name = "Contributor" - principal_id = azuread_service_principal.azure_devops.object_id -} - -# Service Principal that is used to run the tests in GitHub Actions -resource "azuread_application" "github" { - display_name = "github-${random_pet.suffix.id}" - - required_resource_access { - resource_app_id = "00000003-0000-0000-c000-000000000000" - - resource_access { - id = "df021288-bdef-4463-88db-98f22de89214" - type = "Role" - } - } - - required_resource_access { - resource_app_id = "00000002-0000-0000-c000-000000000000" - - resource_access { - id = "1cda74f2-2616-4834-b122-5cb1b07f8a59" - type = "Role" - } - resource_access { - id = "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175" - type = "Role" - } - } -} - -resource "azuread_application_password" "github" { - display_name = "password" - application_object_id = azuread_application.github.object_id -} - -resource "azuread_service_principal" "github" { - application_id = azuread_application.github.application_id -} - -data "azurerm_storage_account" "terraform_state" { - resource_group_name = "terraform-state" - name = "terraformstate0419" -} - -resource "azurerm_role_assignment" "github_resource_group" { - scope = data.azurerm_subscription.current.id - role_definition_name = "Contributor" - principal_id = azuread_service_principal.github.object_id -} - -resource "azurerm_role_assignment" "github_acr" { - scope = azurerm_container_registry.this.id - role_definition_name = "Owner" - principal_id = azuread_service_principal.github.object_id -} - -resource "azurerm_key_vault_access_policy" "github_keyvault_secret_read" { - key_vault_id = azurerm_key_vault.this.id - tenant_id = data.azurerm_client_config.current.tenant_id - object_id = azuread_service_principal.github.object_id - - secret_permissions = [ - "Get", - "List", - ] -} diff --git a/tests/azure/terraform/shared/variables.tf b/tests/azure/terraform/shared/variables.tf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/azure/util_test.go b/tests/azure/util_test.go deleted file mode 100644 index 7c4f4083d4..0000000000 --- a/tests/azure/util_test.go +++ /dev/null @@ -1,457 +0,0 @@ -/* -Copyright 2021 The Flux authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package test - -import ( - "context" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - corev1 "k8s.io/api/core/v1" - apimeta "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - helmv2beta1 "github.com/fluxcd/helm-controller/api/v2beta1" - automationv1beta1 "github.com/fluxcd/image-automation-controller/api/v1beta1" - reflectorv1beta2 "github.com/fluxcd/image-reflector-controller/api/v1beta2" - kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" - notiv1 "github.com/fluxcd/notification-controller/api/v1" - notiv1beta2 "github.com/fluxcd/notification-controller/api/v1beta2" - "github.com/fluxcd/pkg/apis/meta" - "github.com/fluxcd/pkg/git" - "github.com/fluxcd/pkg/git/gogit" - "github.com/fluxcd/pkg/git/repository" - sourcev1 "github.com/fluxcd/source-controller/api/v1" - sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" - extgogit "github.com/go-git/go-git/v5" - gitconfig "github.com/go-git/go-git/v5/config" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/go-git/go-git/v5/plumbing/transport/http" -) - -const defaultBranch = "main" - -// getKubernetesCredentials returns a path to a kubeconfig file and a kube client instance. -func getKubernetesCredentials(kubeconfig, aksHost, aksCert, aksKey, aksCa string) (string, client.Client, error) { - tmpDir, err := os.MkdirTemp("", "*-azure-e2e") - if err != nil { - return "", nil, err - } - kubeconfigPath := fmt.Sprintf("%s/kubeconfig", tmpDir) - os.WriteFile(kubeconfigPath, []byte(kubeconfig), 0750) - kubeCfg := &rest.Config{ - Host: aksHost, - TLSClientConfig: rest.TLSClientConfig{ - CertData: []byte(aksCert), - KeyData: []byte(aksKey), - CAData: []byte(aksCa), - }, - } - err = sourcev1b2.AddToScheme(scheme.Scheme) - if err != nil { - return "", nil, err - } - err = sourcev1.AddToScheme(scheme.Scheme) - if err != nil { - return "", nil, err - } - err = kustomizev1.AddToScheme(scheme.Scheme) - if err != nil { - return "", nil, err - } - err = helmv2beta1.AddToScheme(scheme.Scheme) - if err != nil { - return "", nil, err - } - err = reflectorv1beta2.AddToScheme(scheme.Scheme) - if err != nil { - return "", nil, err - } - err = automationv1beta1.AddToScheme(scheme.Scheme) - if err != nil { - return "", nil, err - } - err = notiv1beta2.AddToScheme(scheme.Scheme) - if err != nil { - return "", nil, err - } - err = notiv1.AddToScheme(scheme.Scheme) - if err != nil { - return "", nil, err - } - kubeClient, err := client.New(kubeCfg, client.Options{Scheme: scheme.Scheme}) - if err != nil { - return "", nil, err - } - return kubeconfigPath, kubeClient, nil -} - -// installFlux adds the core Flux components to the cluster specified in the kubeconfig file. -func installFlux(ctx context.Context, kubeClient client.Client, kubeconfigPath, repoUrl, azdoPat string, sp spConfig) error { - namespace := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "flux-system", - }, - } - _, err := controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &namespace, func() error { - return nil - }) - - azureSp := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "azure-sp", Namespace: "flux-system"}} - _, err = controllerutil.CreateOrUpdate(ctx, kubeClient, azureSp, func() error { - azureSp.StringData = map[string]string{ - "AZURE_TENANT_ID": sp.tenantId, - "AZURE_CLIENT_ID": sp.clientId, - "AZURE_CLIENT_SECRET": sp.clientSecret, - } - return nil - }) - if err != nil { - return err - } - - //// Install Flux and push files to git repository - repo, _, err := getRepository(repoUrl, defaultBranch, true, azdoPat) - if err != nil { - return fmt.Errorf("error cloning repositoriy: %w", err) - } - - kustomizeYaml := ` -resources: - - gotk-components.yaml - - gotk-sync.yaml -patchesStrategicMerge: - - |- - apiVersion: apps/v1 - kind: Deployment - metadata: - name: kustomize-controller - namespace: flux-system - spec: - template: - spec: - containers: - - name: manager - envFrom: - - secretRef: - name: azure-sp - - |- - apiVersion: apps/v1 - kind: Deployment - metadata: - name: source-controller - namespace: flux-system - spec: - template: - spec: - containers: - - name: manager - envFrom: - - secretRef: - name: azure-sp -` - - files := make(map[string]io.Reader) - files["clusters/e2e/flux-system/kustomization.yaml"] = strings.NewReader(kustomizeYaml) - files["clusters/e2e/flux-system/gotk-components.yaml"] = strings.NewReader("") - files["clusters/e2e/flux-system/gotk-sync.yaml"] = strings.NewReader("") - err = commitAndPushAll(repo, files, defaultBranch) - if err != nil { - return fmt.Errorf("error committing and pushing manifests: %w", err) - } - - bootstrapCmd := fmt.Sprintf("flux bootstrap git --url=%s --password=%s --kubeconfig=%s"+ - " --token-auth --path=clusters/e2e --components-extra image-reflector-controller,image-automation-controller", - repoUrl, azdoPat, kubeconfigPath) - if err := runCommand(context.Background(), 10*time.Minute, "./", bootstrapCmd); err != nil { - return fmt.Errorf("error running bootstrap: %w", err) - } - - return nil -} - -func runCommand(ctx context.Context, timeout time.Duration, dir, command string) error { - timeoutCtx, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - cmd := exec.CommandContext(timeoutCtx, "bash", "-c", command) - cmd.Dir = dir - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("failure to run command %s: %v", string(output), err) - } - return nil -} - -// verifyGitAndKustomization checks that the gitrespository and kustomization combination are working properly. -func verifyGitAndKustomization(ctx context.Context, kubeClient client.Client, namespace, name string) error { - nn := types.NamespacedName{ - Name: name, - Namespace: namespace, - } - source := &sourcev1.GitRepository{} - err := kubeClient.Get(ctx, nn, source) - if err != nil { - return err - } - if apimeta.IsStatusConditionPresentAndEqual(source.Status.Conditions, meta.ReadyCondition, metav1.ConditionTrue) == false { - return fmt.Errorf("source condition not ready") - } - kustomization := &kustomizev1.Kustomization{} - err = kubeClient.Get(ctx, nn, kustomization) - if err != nil { - return err - } - if apimeta.IsStatusConditionPresentAndEqual(kustomization.Status.Conditions, meta.ReadyCondition, metav1.ConditionTrue) == false { - return fmt.Errorf("kustomization condition not ready") - } - return nil -} - -func setupNamespace(ctx context.Context, kubeClient client.Client, repoUrl, password, name string) error { - namespace := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - } - _, err := controllerutil.CreateOrUpdate(ctx, kubeClient, &namespace, func() error { - return nil - }) - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "https-credentials", - Namespace: name, - }, - } - _, err = controllerutil.CreateOrUpdate(ctx, kubeClient, &secret, func() error { - secret.StringData = map[string]string{ - "username": "git", - "password": password, - } - return nil - }) - source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}} - _, err = controllerutil.CreateOrUpdate(ctx, kubeClient, source, func() error { - source.Spec = sourcev1.GitRepositorySpec{ - Interval: metav1.Duration{ - Duration: 1 * time.Minute, - }, - Reference: &sourcev1.GitRepositoryRef{ - Branch: name, - }, - SecretRef: &meta.LocalObjectReference{ - Name: "https-credentials", - }, - URL: repoUrl, - } - return nil - }) - if err != nil { - return err - } - kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}} - _, err = controllerutil.CreateOrUpdate(ctx, kubeClient, kustomization, func() error { - kustomization.Spec = kustomizev1.KustomizationSpec{ - SourceRef: kustomizev1.CrossNamespaceSourceReference{ - Kind: sourcev1.GitRepositoryKind, - Name: source.Name, - Namespace: source.Namespace, - }, - Interval: metav1.Duration{ - Duration: 1 * time.Minute, - }, - Prune: true, - } - return nil - }) - if err != nil { - return err - } - return nil -} - -func getRepository(repoURL, branchName string, overrideBranch bool, password string) (*gogit.Client, string, error) { - checkoutBranch := defaultBranch - if overrideBranch == false { - checkoutBranch = branchName - } - - tmpDir, err := os.MkdirTemp("", "*-repository") - if err != nil { - return nil, "", err - } - c, err := gogit.NewClient(tmpDir, &git.AuthOptions{ - Transport: git.HTTPS, - Username: "git", - Password: password, - }) - if err != nil { - return nil, "", err - } - - _, err = c.Clone(context.Background(), repoURL, repository.CloneConfig{ - CheckoutStrategy: repository.CheckoutStrategy{ - Branch: checkoutBranch, - }, - }) - if err != nil { - return nil, "", err - } - - err = c.SwitchBranch(context.Background(), branchName) - if err != nil { - return nil, "", err - } - - return c, tmpDir, nil -} - -func addFile(dir, path, content string) error { - err := os.WriteFile(filepath.Join(dir, path), []byte(content), 0777) - if err != nil { - return err - } - return nil -} - -func commitAndPushAll(client *gogit.Client, files map[string]io.Reader, branchName string) error { - repo, err := extgogit.PlainOpen(client.Path()) - if err != nil { - return err - } - - wt, err := repo.Worktree() - if err != nil { - return err - } - - err = wt.Checkout(&extgogit.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName(branchName), - Force: true, - }) - if err != nil { - return err - } - - f := repository.WithFiles(files) - _, err = client.Commit(git.Commit{ - Author: git.Signature{ - Name: "git", - Email: "test@example.com", - When: time.Now(), - }, - Message: "add file", - }, f) - - if err != nil { - return err - } - - err = client.Push(context.Background(), repository.PushConfig{}) - if err != nil { - return err - } - - return nil -} - -func createTagAndPush(client *gogit.Client, branchName, newTag, password string) error { - repo, err := extgogit.PlainOpen(client.Path()) - if err != nil { - return err - } - - ref, err := repo.Reference(plumbing.NewBranchReferenceName(branchName), false) - if err != nil { - return err - } - - tags, err := repo.TagObjects() - if err != nil { - return err - } - - err = tags.ForEach(func(tag *object.Tag) error { - if tag.Name == newTag { - err = repo.DeleteTag(tag.Name) - if err != nil { - return err - } - } - - return nil - }) - if err != nil { - return err - } - - sig := &object.Signature{ - Name: "git", - Email: "test@example.com", - When: time.Now(), - } - - _, err = repo.CreateTag(newTag, ref.Hash(), &extgogit.CreateTagOptions{ - Tagger: sig, - Message: "create tag", - }) - if err != nil { - return err - } - - auth := &http.BasicAuth{ - Username: "git", - Password: password, - } - - po := &extgogit.PushOptions{ - RemoteName: "origin", - Progress: os.Stdout, - RefSpecs: []gitconfig.RefSpec{gitconfig.RefSpec("refs/tags/*:refs/tags/*")}, - Auth: auth, - } - if err := repo.Push(po); err != nil { - return err - } - - return nil -} - -func getTestManifest(namespace string) string { - return fmt.Sprintf(` -apiVersion: v1 -kind: Namespace -metadata: - name: %s ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: foobar - namespace: %s -`, namespace, namespace) -} diff --git a/tests/image-automation/auto.yaml b/tests/image-automation/auto.yaml index 8f71c881be..a0f7bce2f2 100644 --- a/tests/image-automation/auto.yaml +++ b/tests/image-automation/auto.yaml @@ -19,7 +19,7 @@ spec: semver: range: 5.2.x --- -apiVersion: image.toolkit.fluxcd.io/v1beta1 +apiVersion: image.toolkit.fluxcd.io/v1beta2 kind: ImageUpdateAutomation metadata: name: flux-system diff --git a/tests/integration/.env.sample b/tests/integration/.env.sample new file mode 100644 index 0000000000..ad9e1fe752 --- /dev/null +++ b/tests/integration/.env.sample @@ -0,0 +1,29 @@ +## Azure +# export TF_VAR_azuredevops_org= +# export TF_VAR_azuredevops_pat= +# export TF_VAR_azure_location= +## Set the following only when authenticating using Service Principal (suited +## for CI environment). +# export ARM_CLIENT_ID= +# export ARM_CLIENT_SECRET= +# export ARM_SUBSCRIPTION_ID= +# export ARM_TENANT_ID= + +## GCP +# export TF_VAR_gcp_project_id= +# export TF_VAR_gcp_zone= +# export TF_VAR_gcp_region= +# export TF_VAR_gcp_keyring= +# export TF_VAR_gcp_crypto_key= +## Email address of a GCP user used for git repository cloning over ssh. +# export TF_VAR_gcp_email= +## Set the following only when using service account. +## Provide absolute path to the service account JSON key file. +# export GOOGLE_APPLICATION_CREDENTIALS= + +## Common variables +# export TF_VAR_tags='{"environment"="dev", "createdat"='"\"$(date -u +x%Y-%m-%d_%Hh%Mm%Ss)\""'}' +## These are not terraform variables +## but they are needed for the bootstrap tests +# export GITREPO_SSH_PATH= +# export GITREPO_SSH_PUB_PATH= diff --git a/tests/integration/Makefile b/tests/integration/Makefile new file mode 100644 index 0000000000..ddae646ae5 --- /dev/null +++ b/tests/integration/Makefile @@ -0,0 +1,32 @@ +GO_TEST_ARGS ?= +PROVIDER_ARG ?= +TEST_TIMEOUT ?= 60m +FLUX_BINARY ?= ../../bin/flux + +test: sops-check + mkdir -p build + cp $(FLUX_BINARY) build/flux + # These two versions of podinfo are pushed to the cloud registry and used in tests for ImageUpdateAutomation + docker pull ghcr.io/stefanprodan/podinfo:6.0.0 + docker pull ghcr.io/stefanprodan/podinfo:6.0.1 + go test -timeout $(TEST_TIMEOUT) -v ./ $(GO_TEST_ARGS) $(PROVIDER_ARG) + +test-azure: + $(MAKE) test PROVIDER_ARG="-provider azure" GO_TEST_ARGS="--tags azure $(GO_TEST_ARGS)" + +test-gcp: + $(MAKE) test PROVIDER_ARG="-provider gcp" + +destroy: + go test -timeout $(TEST_TIMEOUT) -v ./ $(GO_TEST_ARGS) $(PROVIDER_ARG) -destroy-only + +destroy-azure: + $(MAKE) destroy PROVIDER_ARG="-provider azure" + +destroy-gcp: + $(MAKE) destroy PROVIDER_ARG="-provider gcp" + +sops-check: +ifeq ($(shell which sops),) + $(error "no sops in PATH, consider installing") +endif diff --git a/tests/integration/README.md b/tests/integration/README.md new file mode 100644 index 0000000000..0fa1f3e32c --- /dev/null +++ b/tests/integration/README.md @@ -0,0 +1,390 @@ +# E2E Tests + +The goal is to verify that Flux integration with cloud providers are actually working now and in the future. +Currently, we only have tests for Azure and GCP. + +## General requirements + +These CLI tools need to be installed for each of the tests to run successfully. + +- Docker CLI for registry login. +- [SOPS CLI](https://github.com/mozilla/sops) for encrypting files +- kubectl for applying certain install manifests. + +## Azure + +### Architecture + +The [azure](./terraform/azure) Terraform creates the AKS cluster and related resources to run the tests. It creates: +- An Azure Container Registry +- An Azure Kubernetes Cluster +- Two Azure DevOps repositories +- Azure EventHub for sending notifications +- An Azure Key Vault + +### Requirements + +- Azure account with an active subscription to be able to create AKS and ACR, + and permission to assign roles. Role assignment is required for allowing AKS workloads to access ACR. +- Azure CLI, need to be logged in using `az login` as a User or as a Service Principal +- An Azure DevOps organization, personal access token and ssh keys for accessing repositories + within the organization. The scope required for the personal access token is: + - `Project and Team` - read, write and manage access + - `Code` - Full + - 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) + for more explanation. + - Azure DevOps only supports RSA keys. Please see + [documentation](https://learn.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops#set-up-ssh-key-authentication) + for how to set up SSH key authentication. + - When using in CI, create a test user and use the test user's PAT and SSH key + for all Azure DevOps interactions. To grant the test user access in Azure + DevOps: + - Go to `Organization Settings` on the sidebar of the organization page. + - Under `General` > `Users`, click on `Add User` and input the user's email, + select `Access Level` of `Basic`. + - Go to `Security` > `Permissions`, click on the `User` tab. + - For the invited user, set the following permissions to `Allow`: + - `General: Create new project`. + - The user will get an email invitation and would need to create a Microsoft + account if they don't have one yet. + +**NOTE:** To use Service Principal (for example in CI environment), set the +`ARM-*` variables in `.env`, source it and authenticate Azure CLI with: +```console +$ az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID +``` + +### Permissions + +Following permissions are needed for provisioning the infrastructure and running +the tests: +- `Microsoft.Kubernetes/*` +- `Microsoft.Resources/*` +- `Microsoft.Authorization/roleAssignments/{Read,Write,Delete}` +- `Microsoft.ContainerRegistry/*` +- `Microsoft.ContainerService/*` +- `Microsoft.KeyVault/*` +- `Microsoft.EventHub/*` + +### IAM and CI setup + +To create the necessary IAM role with all the permissions, set up CI secrets and +variables using +[azure-gh-actions](https://github.com/fluxcd/test-infra/tree/main/tf-modules/azure/github-actions) +use the terraform configuration below. Please make sure all the requirements of +azure-gh-actions are followed before running it. + +**NOTE:** When running the following for a repo under an organization, set the +environment variable `GITHUB_ORGANIZATION` if setting the `owner` in the +`github` provider doesn't work. + +```hcl +provider "github" { + owner = "fluxcd" +} + +resource "tls_private_key" "privatekey" { + algorithm = "RSA" + rsa_bits = 4096 +} + +module "azure_gh_actions" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/azure/github-actions" + + azure_owners = ["owner-id-1", "owner-id-2"] + azure_app_name = "flux2-e2e" + azure_app_description = "flux2 e2e" + azure_app_secret_name = "flux2-e2e" + azure_permissions = [ + "Microsoft.Kubernetes/*", + "Microsoft.Resources/*", + "Microsoft.Authorization/roleAssignments/Read", + "Microsoft.Authorization/roleAssignments/Write", + "Microsoft.Authorization/roleAssignments/Delete", + "Microsoft.ContainerRegistry/*", + "Microsoft.ContainerService/*", + "Microsoft.KeyVault/*", + "Microsoft.EventHub/*" + ] + azure_location = "eastus" + + github_project = "flux2" + + github_secret_client_id_name = "AZ_ARM_CLIENT_ID" + github_secret_client_secret_name = "AZ_ARM_CLIENT_SECRET" + github_secret_subscription_id_name = "AZ_ARM_SUBSCRIPTION_ID" + github_secret_tenant_id_name = "AZ_ARM_TENANT_ID" + + github_secret_custom = { + "TF_VAR_azuredevops_org" = "", + "TF_VAR_azuredevops_pat" = "", + "AZURE_GITREPO_SSH_CONTENTS" = base64encode(tls_private_key.privatekey.private_key_openssh), + "AZURE_GITREPO_SSH_PUB_CONTENTS" = base64encode(tls_private_key.privatekey.public_key_openssh) + } +} + +output "publickey" { + value = tls_private_key.privatekey.public_key_openssh +} +``` + +Copy the `publickey` output printed after applying, or run `terraform output` to +print it again, and add it in the Azure DevOps SSH public keys under the user +account that'll be used by flux in the tests. + +**NOTE:** The environment variables used above are for the GitHub workflow that +runs the tests. Change the variable names if needed accordingly. + +## GCP + +### Architecture + +The [gcp](./terraform/gcp) terraform files create the GKE cluster and related resources to run the tests. It creates: +- A Google Container Registry and Artifact Registry +- A Google Kubernetes Cluster +- Two Google Cloud Source Repositories +- A Google Pub/Sub Topic and a subscription to the service that would be used in the tests + +Note: It doesn't create Google KMS keyrings and crypto keys because these cannot be destroyed. Instead, you have +to pass in the crypto key and keyring that would be used to test the sops encryption in Flux. Please see `.env.sample` +for the terraform variables + +### Requirements + +- GCP account with an active project to be able to create GKE and GCR, and permission to assign roles. +- Existing GCP KMS keyring and crypto key. + - [Create a Keyring](https://cloud.google.com/kms/docs/create-key-ring) in + `global` location. + - [Create a Crypto Key](https://cloud.google.com/kms/docs/create-key) with + symmetric algorithm for encryption and decryption, and software based + protection level. +- gcloud CLI, need to be logged in using `gcloud auth login` as a User (not a + Service Account), configure application default credentials with `gcloud auth + application-default login` and docker credential helper with `gcloud auth configure-docker`. + + **NOTE:** To use Service Account (for example in CI environment), set + `GOOGLE_APPLICATION_CREDENTIALS` variable in `.env` with the path to the JSON + key file, source it and authenticate gcloud CLI with: + ```console + $ gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS + ``` + Depending on the Container/Artifact Registry host used in the test, authenticate + docker accordingly + ```console + $ gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://us-central1-docker.pkg.dev + ``` + In this case, the GCP client in terraform uses the Service Account to + authenticate and the gcloud CLI is used only to authenticate with Google + Container Registry and Google Artifact Registry. + + **NOTE FOR CI USAGE:** When saving the JSON key file as a CI secret, compress + the file content with + ```console + $ cat key.json | jq -r tostring + ``` + to prevent aggressive masking in the logs. Refer + [aggressive replacement in logs](https://github.com/google-github-actions/auth/blob/v1.1.0/docs/TROUBLESHOOTING.md#aggressive--replacement-in-logs) + for more details. +- Register [SSH Keys with Google Cloud](https://cloud.google.com/source-repositories/docs/authentication#ssh) + - Google Cloud supports these three SSH key types: RSA (only for keys with + more than 2048 bits), ECDSA and ED25519. + - The SSH user doesn't have to be a member of the GCP project. The terraform + setup will grant the user permissions to the repository. Visit + https://source.cloud.google.com, login or create a GCP account with the SSH + user's email address and add SSH keys in the account. Set this email as the + value for the environment variable `TF_VAR_gcp_email` in `.env` file to be + used as a terraform variable. + + **Note:** Google doesn't allow a SSH key to be associated with a service + account email address. Therefore, there has to be an actual user that the SSH + key is registered to. + +### Permissions + +Following roles are needed for provisioning the infrastructure and running the tests: + +- Compute Instance Admin (v1) - `roles/compute.instanceAdmin.v1` +- Kubernetes Engine Admin - `roles/container.admin` +- Service Account User - `roles/iam.serviceAccountUser` +- Service Account Token Creator - `roles/iam.serviceAccountTokenCreator` +- Artifact Registry Administrator - `roles/artifactregistry.admin` +- Artifact Registry Repository Administrator - `roles/artifactregistry.repoAdmin` +- Cloud KMS Admin - `roles/cloudkms.admin` +- Cloud KMS CryptoKey Encrypter - `roles/cloudkms.cryptoKeyEncrypter` +- Source Repository Administrator - `roles/source.admin` +- Pub/Sub Admin - `roles/pubsub.admin` + +### IAM and CI setup + +To create the necessary IAM role with all the permissions, set up CI secrets and +variables using +[gcp-gh-actions](https://github.com/fluxcd/test-infra/tree/main/tf-modules/gcp/github-actions) +use the terraform configuration below. Please make sure all the requirements of +gcp-gh-actions are followed before running it. + +**NOTE:** When running the following for a repo under an organization, set the +environment variable `GITHUB_ORGANIZATION` if setting the `owner` in the +`github` provider doesn't work. + +```hcl +provider "google" {} + +provider "github" { + owner = "fluxcd" +} + +resource "tls_private_key" "privatekey" { + algorithm = "RSA" + rsa_bits = 4096 +} + +module "gcp_gh_actions" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/github-actions" + + gcp_service_account_id = "flux2-e2e-test" + gcp_service_account_name = "flux2-e2e-test" + gcp_service_account_description = "For running fluxcd/flux2 e2e tests." + gcp_roles = [ + "roles/compute.instanceAdmin.v1", + "roles/container.admin", + "roles/iam.serviceAccountUser", + "roles/iam.serviceAccountTokenCreator", + "roles/artifactregistry.admin", + "roles/artifactregistry.repoAdmin", + "roles/cloudkms.admin", + "roles/cloudkms.cryptoKeyEncrypter", + "roles/source.admin", + "roles/pubsub.admin" + ] + + github_project = "flux2" + + github_secret_credentials_name = "FLUX2_E2E_GOOGLE_CREDENTIALS" + + github_secret_custom = { + "TF_VAR_gcp_keyring" = "", + "TF_VAR_gcp_crypto_key" = "", + "TF_VAR_gcp_email" = "", + "GCP_GITREPO_SSH_CONTENTS" = base64encode(tls_private_key.privatekey.private_key_openssh), + "GCP_GITREPO_SSH_PUB_CONTENTS" = base64encode(tls_private_key.privatekey.public_key_openssh) + } +} + +output "publickey" { + value = tls_private_key.privatekey.public_key_openssh +} +``` + +Copy the `publickey` output printed after applying, or run `terraform output` to +print it again, and add it in the Google Source Repository SSH public keys under +the user account with email address referred in `TF_VAR_gcp_email` above. + +**NOTE:** The environment variables used above are for the GitHub workflow that +runs the tests. Change the variable names if needed accordingly. + +## Tests + +Each test run is initiated by running `terraform apply` in the provider's terraform directory e.g terraform apply, +it does this by using the [tftestenv package](https://github.com/fluxcd/test-infra/blob/main/tftestenv/testenv.go) +within the `fluxcd/test-infra` repository. It then reads the output of the Terraform to get information needed +for the tests like the kubernetes client ID, the cloud repository urls, the key vault ID etc. This means that +a lot of the communication with the cloud provider API is offset to Terraform instead of requiring it to be implemented in the test. + +The following tests are currently implemented: + +- Flux can be successfully installed on the cluster using the Flux CLI +- source-controller can clone cloud provider repositories (Azure DevOps, Google Cloud Source Repositories) (https+ssh) +- image-reflector-controller can list tags from provider container Registry image repositories +- kustomize-controller can decrypt secrets using SOPS and provider key vault +- image-automation-controller can create branches and push to cloud repositories (https+ssh) +- source-controller can pull charts from cloud provider container registry Helm repositories +- notification-controller can forward events to cloud Events Service(EventHub for Azure and Google Pub/Sub) + +The following tests are run only for Azure since it is supported in the notification-controller: + +- notification-controller can send commit status to Azure DevOps + +### Running tests locally + +1. Ensure that you have the Flux CLI binary that is to be tested built and ready. You can build it by running + `make build` at the root of this repository. The binary is located at `./bin` directory at the root and by default + this is where the Makefile copies the binary for the tests from. If you have it in a different location, you can set it + with the `FLUX_BINARY` variable +2. Copy `.env.sample` to `.env` and add the values for the different variables for the provider that you are running the tests for. +3. Run `make test-`, setting the location of the flux binary with `FLUX_BINARY` variable + +```console +$ make test-azure +make test PROVIDER_ARG="-provider azure" +# These two versions of podinfo are pushed to the cloud registry and used in tests for ImageUpdateAutomation +mkdir -p build +cp ../../bin/flux build/flux +docker pull ghcr.io/stefanprodan/podinfo:6.0.0 +6.0.0: Pulling from stefanprodan/podinfo +Digest: sha256:e7eeab287181791d36c82c904206a845e30557c3a4a66a8143fa1a15655dae97 +Status: Image is up to date for ghcr.io/stefanprodan/podinfo:6.0.0 +ghcr.io/stefanprodan/podinfo:6.0.0 +docker pull ghcr.io/stefanprodan/podinfo:6.0.1 +6.0.1: Pulling from stefanprodan/podinfo +Digest: sha256:1169f220a670cf640e45e1a7ac42dc381a441e9d4b7396432cadb75beb5b5d68 +Status: Image is up to date for ghcr.io/stefanprodan/podinfo:6.0.1 +ghcr.io/stefanprodan/podinfo:6.0.1 +go test -timeout 60m -v ./ -existing -provider azure --tags=integration +2023/03/24 02:32:25 Setting up azure e2e test infrastructure +2023/03/24 02:32:25 Terraform binary: /usr/local/bin/terraform +2023/03/24 02:32:25 Init Terraform +....[some output has been cut out] +2023/03/24 02:39:33 helm repository condition not ready +--- PASS: TestACRHelmRelease (15.31s) +=== RUN TestKeyVaultSops +--- PASS: TestKeyVaultSops (15.98s) +PASS +2023/03/24 02:40:12 Destroying environment... +ok github.com/fluxcd/flux2/tests/integration 947.341s +``` + +In the above, the test created a build directory build/ and the flux cli binary is copied build/flux. It would be used +to bootstrap Flux on the cluster. You can configure the location of the Flux CLI binary by setting the FLUX_BINARY variable. +We also pull two version of `ghcr.io/stefanprodan/podinfo` image. These images are pushed to the cloud provider's +Container Registry and used to test `ImageRepository` and `ImageUpdateAutomation`. The terraform resources get created +and the tests are run. + +If not configured explicitly to retain the infrastructure, at the end of the +test, the test infrastructure is deleted. In case of any failure due to which +the resources don't get deleted, the `make destroy-*` commands can be run for +the respective provider. This will run terraform destroy in the respective +provider's terraform configuration directory. This can be used to quickly +destroy the infrastructure without going through the provision-test-destroy +steps. + +### Debugging the tests + +For debugging environment provisioning, enable verbose output with `-verbose` test flag. + +```sh +make test-azure GO_TEST_ARGS="-verbose" +``` + +The test environment is destroyed at the end by default. Run the tests with -retain flag to retain the created test infrastructure. + +```sh +make test-azure GO_TEST_ARGS="-retain" +``` +The tests require the infrastructure state to be clean. For re-running the tests with a retained infrastructure, set -existing flag. + +```sh +make test-azure GO_TEST_ARGS="-retain -existing" +``` + +To delete an existing infrastructure created with -retain flag: + +```sh +make test-azure GO_TEST_ARGS="-existing" +``` + +To debug issues on the cluster created by the test (provided you passed in the `-retain` flag): + +```sh +export KUBECONFIG=./build/kubeconfig +kubectl get pods +``` diff --git a/tests/integration/azure_specific_test.go b/tests/integration/azure_specific_test.go new file mode 100644 index 0000000000..7f84c76b63 --- /dev/null +++ b/tests/integration/azure_specific_test.go @@ -0,0 +1,210 @@ +//go:build azure +// +build azure + +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "context" + "fmt" + "io" + "log" + "strings" + "testing" + "time" + + giturls "github.com/chainguard-dev/git-urls" + "github.com/microsoft/azure-devops-go-api/azuredevops" + "github.com/microsoft/azure-devops-go-api/azuredevops/git" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + notiv1 "github.com/fluxcd/notification-controller/api/v1" + notiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3" + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1" +) + +func TestAzureDevOpsCommitStatus(t *testing.T) { + g := NewWithT(t) + + ctx := context.TODO() + branchName := "commit-status" + testID := branchName + randStringRunes(5) + manifest := `apiVersion: v1 +kind: ConfigMap +metadata: + name: foobar` + + repoUrl := getTransportURL(cfg.applicationRepository) + tmpDir := t.TempDir() + c, err := getRepository(ctx, tmpDir, repoUrl, defaultBranch, cfg.defaultAuthOpts) + g.Expect(err).ToNot(HaveOccurred()) + files := make(map[string]io.Reader) + files["configmap.yaml"] = strings.NewReader(manifest) + err = commitAndPushAll(ctx, c, files, branchName) + g.Expect(err).ToNot(HaveOccurred()) + + modifyKsSpec := func(spec *kustomizev1.KustomizationSpec) { + spec.HealthChecks = []meta.NamespacedObjectKindReference{ + { + APIVersion: "v1", + Kind: "ConfigMap", + Name: "foobar", + Namespace: testID, + }, + } + } + err = setUpFluxConfig(ctx, testID, nsConfig{ + ref: &sourcev1.GitRepositoryRef{ + Branch: branchName, + }, + repoURL: repoUrl, + path: "./", + modifyKsSpec: modifyKsSpec, + }) + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + err := tearDownFluxConfig(ctx, testID) + if err != nil { + log.Printf("failed to delete resources in '%s' namespace: %s", testID, err) + } + }) + + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv, testID, testID) + if err != nil { + return false + } + return true + }, testTimeout, testInterval) + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "azuredevops-token", + Namespace: testID, + }, + StringData: map[string]string{ + "token": cfg.gitPat, + }, + } + g.Expect(testEnv.Create(ctx, &secret)).To(Succeed()) + defer testEnv.Delete(ctx, &secret) + + provider := notiv1beta3.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "azuredevops", + Namespace: testID, + }, + Spec: notiv1beta3.ProviderSpec{ + Type: "azuredevops", + Address: repoUrl, + SecretRef: &meta.LocalObjectReference{ + Name: "azuredevops-token", + }, + }, + } + g.Expect(testEnv.Create(ctx, &provider)).To(Succeed()) + defer testEnv.Delete(ctx, &provider) + + alert := notiv1beta3.Alert{ + ObjectMeta: metav1.ObjectMeta{ + Name: "azuredevops", + Namespace: testID, + }, + Spec: notiv1beta3.AlertSpec{ + ProviderRef: meta.LocalObjectReference{ + Name: provider.Name, + }, + EventSources: []notiv1.CrossNamespaceObjectReference{ + { + Kind: "Kustomization", + Name: testID, + Namespace: testID, + }, + }, + }, + } + g.Expect(testEnv.Create(ctx, &alert)).To(Succeed()) + defer testEnv.Delete(ctx, &alert) + + url, err := ParseAzureDevopsURL(repoUrl) + g.Expect(err).ToNot(HaveOccurred()) + + rev, err := c.Head() + g.Expect(err).ToNot(HaveOccurred()) + + connection := azuredevops.NewPatConnection(url.OrgURL, cfg.gitPat) + client, err := git.NewClient(ctx, connection) + g.Expect(err).ToNot(HaveOccurred()) + getArgs := git.GetStatusesArgs{ + Project: &url.Project, + RepositoryId: &url.Repo, + CommitId: &rev, + } + g.Eventually(func() bool { + statuses, err := client.GetStatuses(ctx, getArgs) + if err != nil { + return false + } + if len(*statuses) != 1 { + return false + } + return true + }, 500*time.Second, 5*time.Second) +} + +type AzureDevOpsURL struct { + OrgURL string + Project string + Repo string +} + +// TODO(somtochiama): move this into fluxcd/pkg and reuse in NC +func ParseAzureDevopsURL(s string) (AzureDevOpsURL, error) { + var args AzureDevOpsURL + u, err := giturls.Parse(s) + if err != nil { + return args, nil + } + + scheme := u.Scheme + if u.Scheme == "ssh" { + scheme = "https" + } + + id := strings.TrimLeft(u.Path, "/") + id = strings.TrimSuffix(id, ".git") + + comp := strings.Split(id, "/") + if len(comp) != 4 { + return args, fmt.Errorf("invalid repository id %q", id) + } + + args = AzureDevOpsURL{ + OrgURL: fmt.Sprintf("%s://%s/%s", scheme, u.Host, comp[0]), + Project: comp[1], + Repo: comp[3], + } + + return args, nil + +} diff --git a/tests/integration/azure_test.go b/tests/integration/azure_test.go new file mode 100644 index 0000000000..6c7cab14e4 --- /dev/null +++ b/tests/integration/azure_test.go @@ -0,0 +1,175 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "context" + "fmt" + "os" + + eventhub "github.com/Azure/azure-event-hubs-go/v3" + "github.com/fluxcd/pkg/git" + "github.com/fluxcd/test-infra/tftestenv" + tfjson "github.com/hashicorp/terraform-json" +) + +const ( + azureDevOpsKnownHosts = "ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H" +) + +// createKubeConfigAKS constructs kubeconfig for an AKS cluster from the +// terraform state output at the given kubeconfig path. +func createKubeConfigAKS(ctx context.Context, state map[string]*tfjson.StateOutput, kcPath string) error { + kubeconfigYaml, ok := state["aks_kubeconfig"].Value.(string) + if !ok || kubeconfigYaml == "" { + return fmt.Errorf("failed to obtain kubeconfig from tf output") + } + return tftestenv.CreateKubeconfigAKS(ctx, kubeconfigYaml, kcPath) +} + +func getTestConfigAKS(ctx context.Context, outputs map[string]*tfjson.StateOutput) (*testConfig, error) { + fleetInfraRepository := outputs["fleet_infra_repository"].Value.(map[string]interface{}) + applicationRepository := outputs["application_repository"].Value.(map[string]interface{}) + + eventHubSas := outputs["event_hub_sas"].Value.(string) + sharedSopsId := outputs["sops_id"].Value.(string) + + kustomizeYaml := ` +resources: + - gotk-components.yaml + - gotk-sync.yaml +patchesStrategicMerge: + - |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: kustomize-controller + namespace: flux-system + spec: + template: + spec: + containers: + - name: manager + env: + - name: AZURE_AUTH_METHOD + value: msi +` + + privateKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPath) + if !ok { + return nil, fmt.Errorf("%s env variable isn't set", envVarGitRepoSSHPath) + } + privateKeyData, err := os.ReadFile(privateKeyFile) + if err != nil { + return nil, fmt.Errorf("error getting azure devops private key, '%s': %w", privateKeyFile, err) + } + + pubKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPubPath) + if !ok { + return nil, fmt.Errorf("%s env variable isn't set", envVarGitRepoSSHPubPath) + } + pubKeyData, err := os.ReadFile(pubKeyFile) + if err != nil { + return nil, fmt.Errorf("error getting ssh pubkey '%s', %w", pubKeyFile, err) + } + + c := make(chan []byte, 10) + closefn, err := setupEventHubHandler(ctx, c, eventHubSas) + + var notificationCfg = notificationConfig{ + notificationChan: c, + providerType: "azureeventhub", + closeChan: closefn, + secret: map[string]string{ + "address": eventHubSas, + }, + } + + config := &testConfig{ + defaultGitTransport: git.HTTP, + gitUsername: git.DefaultPublicKeyAuthUser, + gitPat: outputs["azure_devops_access_token"].Value.(string), + gitPrivateKey: string(privateKeyData), + gitPublicKey: string(pubKeyData), + knownHosts: azureDevOpsKnownHosts, + fleetInfraRepository: gitUrl{ + http: fleetInfraRepository["http"].(string), + ssh: fleetInfraRepository["ssh"].(string), + }, + applicationRepository: gitUrl{ + http: applicationRepository["http"].(string), + ssh: applicationRepository["ssh"].(string), + }, + notificationCfg: notificationCfg, + sopsArgs: fmt.Sprintf("--azure-kv %s", sharedSopsId), + sopsSecretData: map[string]string{ + "sops.azure-kv": fmt.Sprintf(`clientId: %s`, outputs["aks_client_id"].Value.(string)), + }, + kustomizationYaml: kustomizeYaml, + } + + opts, err := authOpts(config.fleetInfraRepository.http, map[string][]byte{ + "password": []byte(config.gitPat), + "username": []byte("git"), + }) + if err != nil { + return nil, err + } + config.defaultAuthOpts = opts + + return config, nil +} + +// registryLoginACR logs into the Azure Container Registries using the +// provider's CLI tools and returns the test repositories. +func registryLoginACR(ctx context.Context, output map[string]*tfjson.StateOutput) (string, error) { + // NOTE: ACR registry accept dynamic repository creation by just pushing a + // new image with a new repository name. + registryURL := output["acr_url"].Value.(string) + if err := tftestenv.RegistryLoginACR(ctx, registryURL); err != nil { + return "", err + } + + return registryURL, nil +} + +func setupEventHubHandler(ctx context.Context, c chan []byte, eventHubSas string) (func(), error) { + hub, err := eventhub.NewHubFromConnectionString(eventHubSas) + if err != nil { + return nil, err + } + + handler := func(ctx context.Context, event *eventhub.Event) error { + c <- event.Data + return nil + } + runtimeInfo, err := hub.GetRuntimeInformation(ctx) + if err != nil { + return nil, err + } + listenerHandler, err := hub.Receive(ctx, runtimeInfo.PartitionIDs[0], handler, eventhub.ReceiveWithLatestOffset()) + if err != nil { + return nil, err + } + + closefn := func() { + listenerHandler.Close(ctx) + hub.Close(ctx) + } + + return closefn, nil +} diff --git a/tests/integration/flux_test.go b/tests/integration/flux_test.go new file mode 100644 index 0000000000..6a82f1e6db --- /dev/null +++ b/tests/integration/flux_test.go @@ -0,0 +1,168 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "context" + "fmt" + "io" + "strings" + "testing" + "time" + + "github.com/fluxcd/pkg/git" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestFluxInstallation(t *testing.T) { + g := NewWithT(t) + ctx := context.TODO() + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv.Client, "flux-system", "flux-system") + if err != nil { + return false + } + return true + }, 60*time.Second, 5*time.Second) +} + +func TestRepositoryCloning(t *testing.T) { + ctx := context.TODO() + branchName := "feature/branch" + tagName := "v1" + + g := NewWithT(t) + + type testStruct struct { + name string + refType string + cloneType git.TransportType + } + + tests := []testStruct{ + { + name: "ssh-feature-branch", + refType: "branch", + cloneType: git.SSH, + }, + { + name: "ssh-v1", + refType: "tag", + cloneType: git.SSH, + }, + } + + // Not all cloud providers have repositories that support authentication with an accessToken + // we don't run http tests for these. + if cfg.gitPat != "" { + httpTests := []testStruct{ + { + name: "https-feature-branch", + refType: "branch", + cloneType: git.HTTP, + }, + { + name: "https-v1", + refType: "tag", + cloneType: git.HTTP, + }, + } + + tests = append(tests, httpTests...) + } + + t.Log("Creating application sources") + url := getTransportURL(cfg.applicationRepository) + tmpDir := t.TempDir() + client, err := getRepository(ctx, tmpDir, url, defaultBranch, cfg.defaultAuthOpts) + g.Expect(err).ToNot(HaveOccurred()) + + files := make(map[string]io.Reader) + for _, tt := range tests { + manifest := `apiVersion: v1 +kind: ConfigMap +metadata: + name: foobar + ` + name := fmt.Sprintf("cloning-test/%s/configmap.yaml", tt.name) + files[name] = strings.NewReader(manifest) + } + + err = commitAndPushAll(ctx, client, files, branchName) + g.Expect(err).ToNot(HaveOccurred()) + err = createTagAndPush(ctx, client, branchName, tagName) + g.Expect(err).ToNot(HaveOccurred()) + + t.Log("Verifying application-gitops namespaces") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + ref := &sourcev1.GitRepositoryRef{ + Branch: branchName, + } + if tt.refType == "tag" { + ref = &sourcev1.GitRepositoryRef{ + Tag: tagName, + } + } + + url := cfg.applicationRepository.http + if tt.cloneType == git.SSH { + url = cfg.applicationRepository.ssh + } + + testID := fmt.Sprintf("%s-%s", tt.name, randStringRunes(5)) + err := setUpFluxConfig(ctx, testID, nsConfig{ + repoURL: url, + ref: ref, + protocol: tt.cloneType, + objectName: testID, + path: fmt.Sprintf("./cloning-test/%s", tt.name), + }) + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + err := tearDownFluxConfig(ctx, testID) + if err != nil { + t.Logf("failed to delete resources in '%s' namespace: %s", tt.name, err) + } + }) + + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID) + if err != nil { + return false + } + return true + }, 120*time.Second, 5*time.Second).Should(BeTrue()) + + // Wait for configmap to be deployed + g.Eventually(func() bool { + nn := types.NamespacedName{Name: "foobar", Namespace: testID} + cm := &corev1.ConfigMap{} + err = testEnv.Get(ctx, nn, cm) + if err != nil { + return false + } + + return true + }, 120*time.Second, 5*time.Second).Should(BeTrue()) + }) + } +} diff --git a/tests/integration/gcp_test.go b/tests/integration/gcp_test.go new file mode 100644 index 0000000000..8d901dcbbf --- /dev/null +++ b/tests/integration/gcp_test.go @@ -0,0 +1,178 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "context" + "fmt" + "io" + "log" + "os" + "strings" + + "cloud.google.com/go/pubsub" + tfjson "github.com/hashicorp/terraform-json" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/fluxcd/pkg/git" + "github.com/fluxcd/test-infra/tftestenv" +) + +const ( + gcpSourceRepoKnownHosts = "[source.developers.google.com]:2022 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBB5Iy4/cq/gt/fPqe3uyMy4jwv1Alc94yVPxmnwNhBzJqEV5gRPiRk5u4/JJMbbu9QUVAguBABxL7sBZa5PH/xY=" +) + +// createKubeConfigGKE constructs kubeconfig for a GKE cluster from the +// terraform state output at the given kubeconfig path. +func createKubeConfigGKE(ctx context.Context, state map[string]*tfjson.StateOutput, kcPath string) error { + kubeconfigYaml, ok := state["gke_kubeconfig"].Value.(string) + if !ok || kubeconfigYaml == "" { + return fmt.Errorf("failed to obtain kubeconfig from tf output") + } + return tftestenv.CreateKubeconfigGKE(ctx, kubeconfigYaml, kcPath) +} + +// registryLoginGCR logs into the Artifact registries using the gcloud +// and returns a list of test repositories. +func registryLoginGCR(ctx context.Context, output map[string]*tfjson.StateOutput) (string, error) { + project := output["gcp_project_id"].Value.(string) + region := output["gcp_region"].Value.(string) + repositoryID := output["artifact_registry_id"].Value.(string) + artifactRegistryURL, artifactRepoURL := tftestenv.GetGoogleArtifactRegistryAndRepository(project, region, repositoryID) + if err := tftestenv.RegistryLoginGCR(ctx, artifactRegistryURL); err != nil { + return "", err + } + + return artifactRepoURL, nil +} + +func getTestConfigGKE(ctx context.Context, outputs map[string]*tfjson.StateOutput) (*testConfig, error) { + sharedSopsId := outputs["sops_id"].Value.(string) + + privateKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPath) + if !ok { + return nil, fmt.Errorf("%s env variable isn't set", envVarGitRepoSSHPath) + } + privateKeyData, err := os.ReadFile(privateKeyFile) + if err != nil { + return nil, fmt.Errorf("error getting gcp source repositories private key, '%s': %w", privateKeyFile, err) + } + + pubKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPubPath) + if !ok { + return nil, fmt.Errorf("%s env variable isn't set", envVarGitRepoSSHPubPath) + } + pubKeyData, err := os.ReadFile(pubKeyFile) + if err != nil { + return nil, fmt.Errorf("error getting ssh pubkey '%s', %w", pubKeyFile, err) + } + + c := make(chan []byte, 10) + projectID := outputs["gcp_project_id"].Value.(string) + topicID := outputs["pubsub_topic"].Value.(string) + + fn, err := setupPubSubReceiver(ctx, c, projectID, topicID) + if err != nil { + return nil, err + } + + var notificationCfg = notificationConfig{ + providerType: "googlepubsub", + providerChannel: topicID, + notificationChan: c, + closeChan: fn, + secret: map[string]string{ + "address": projectID, + }, + } + + config := &testConfig{ + defaultGitTransport: git.SSH, + gitUsername: "git", + gitPrivateKey: string(privateKeyData), + gitPublicKey: string(pubKeyData), + knownHosts: gcpSourceRepoKnownHosts, + fleetInfraRepository: gitUrl{ + ssh: outputs["fleet_infra_repository"].Value.(string), + }, + applicationRepository: gitUrl{ + ssh: outputs["application_repository"].Value.(string), + }, + notificationCfg: notificationCfg, + sopsArgs: fmt.Sprintf("--gcp-kms %s", sharedSopsId), + } + + opts, err := authOpts(config.fleetInfraRepository.ssh, map[string][]byte{ + "identity": []byte(config.gitPrivateKey), + "known_hosts": []byte(config.knownHosts), + }) + if err != nil { + return nil, err + } + + config.defaultAuthOpts = opts + + // In Azure, the repository is initialized with a default branch through + // terraform. We have to do it manually here for GCP to prevent errors + // when trying to clone later. We only need to do it for the application repository + // since flux bootstrap pushes to the main branch. + files := make(map[string]io.Reader) + files["README.md"] = strings.NewReader("# Flux test repo") + tmpDir, err := os.MkdirTemp("", "*-flux-test") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpDir) + + client, err := getRepository(context.Background(), tmpDir, config.applicationRepository.ssh, defaultBranch, config.defaultAuthOpts) + if err != nil { + return nil, err + } + err = commitAndPushAll(context.Background(), client, files, defaultBranch) + if err != nil { + return nil, err + } + + return config, nil +} + +func setupPubSubReceiver(ctx context.Context, c chan []byte, projectID string, topicID string) (func(), error) { + newCtx, cancel := context.WithCancel(ctx) + pubsubClient, err := pubsub.NewClient(newCtx, projectID) + if err != nil { + cancel() + return nil, fmt.Errorf("error creating pubsub client: %s", err) + } + + sub := pubsubClient.Subscription(topicID) + go func() { + err = sub.Receive(ctx, func(ctx context.Context, message *pubsub.Message) { + c <- message.Data + message.Ack() + }) + if err != nil && status.Code(err) != codes.Canceled { + log.Printf("error receiving message in subscription: %s\n", err) + return + } + }() + + return func() { + cancel() + pubsubClient.Close() + }, nil +} diff --git a/tests/integration/go.mod b/tests/integration/go.mod new file mode 100644 index 0000000000..1873221306 --- /dev/null +++ b/tests/integration/go.mod @@ -0,0 +1,152 @@ +module github.com/fluxcd/flux2/tests/integration + +go 1.22.0 + +require ( + cloud.google.com/go/pubsub v1.38.0 + github.com/Azure/azure-event-hubs-go/v3 v3.6.2 + github.com/chainguard-dev/git-urls v1.0.2 + github.com/fluxcd/helm-controller/api v1.0.1 + github.com/fluxcd/image-automation-controller/api v0.38.0 + github.com/fluxcd/image-reflector-controller/api v0.32.0 + github.com/fluxcd/kustomize-controller/api v1.3.0 + github.com/fluxcd/notification-controller/api v1.3.0 + github.com/fluxcd/pkg/apis/event v0.9.0 + github.com/fluxcd/pkg/apis/meta v1.5.0 + github.com/fluxcd/pkg/git v0.19.0 + github.com/fluxcd/pkg/git/gogit v0.19.0 + github.com/fluxcd/pkg/runtime v0.47.1 + github.com/fluxcd/source-controller/api v1.3.0 + github.com/fluxcd/test-infra/tftestenv v0.0.0-20240429114247-01ecf2cc78e4 + github.com/go-git/go-git/v5 v5.12.0 + github.com/google/go-containerregistry v0.19.1 + github.com/hashicorp/terraform-exec v0.20.0 + github.com/hashicorp/terraform-json v0.21.0 + github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 + github.com/onsi/gomega v1.33.1 + google.golang.org/grpc v1.63.2 + k8s.io/api v0.30.0 + k8s.io/apimachinery v0.30.0 + k8s.io/client-go v0.30.0 + sigs.k8s.io/controller-runtime v0.18.1 +) + +require ( + cloud.google.com/go v0.112.2 // indirect + cloud.google.com/go/auth v0.3.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go/iam v1.1.7 // indirect + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/azure-amqp-common-go/v4 v4.2.0 // indirect + github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect + github.com/Azure/go-amqp v1.0.0 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.28 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect + github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/devigned/tab v0.1.1 // indirect + github.com/docker/cli v24.0.9+incompatible // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v24.0.9+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fluxcd/pkg/apis/acl v0.3.0 // indirect + github.com/fluxcd/pkg/apis/kustomize v1.5.0 // indirect + github.com/fluxcd/pkg/ssh v0.13.0 // indirect + github.com/fluxcd/pkg/version v0.4.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.3 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/hc-install v0.6.2 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.16.5 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc3 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/sirupsen/logrus v1.9.1 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/vbatts/tar-split v0.11.3 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/zclconf/go-cty v1.14.1 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/oauth2 v0.19.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.20.0 // indirect + google.golang.org/api v0.177.0 // indirect + google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect + google.golang.org/protobuf v1.34.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.30.0 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 // indirect + k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/tests/azure/go.sum b/tests/integration/go.sum similarity index 51% rename from tests/azure/go.sum rename to tests/integration/go.sum index c7004d0af4..180de21633 100644 --- a/tests/azure/go.sum +++ b/tests/integration/go.sum @@ -1,9 +1,26 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw= +cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= +cloud.google.com/go/auth v0.3.0 h1:PRyzEpGfx/Z9e8+lHsbkoUVXD0gnu4MNmm7Gp8TQNIs= +cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM= +cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA= +cloud.google.com/go/kms v1.15.8 h1:szIeDCowID8th2i8XE4uRev5PMxQFqW+JjwYxL9h6xs= +cloud.google.com/go/kms v1.15.8/go.mod h1:WoUHcDjD9pluCg7pNds131awnH429QGvRM3N/4MyoVs= +cloud.google.com/go/pubsub v1.38.0 h1:J1OT7h51ifATIedjqk/uBNPh+1hkvUaH4VKbz4UuAsc= +cloud.google.com/go/pubsub v1.38.0/go.mod h1:IPMJSWSus/cu57UyR01Jqa/bNOQA+XnPF6Z4dKW4fAA= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/azure-amqp-common-go/v4 v4.2.0 h1:q/jLx1KJ8xeI8XGfkOWMN9XrXzAfVTkyvCxPvHCjd2I= github.com/Azure/azure-amqp-common-go/v4 v4.2.0/go.mod h1:GD3m/WPPma+621UaU6KNjKEo5Hl09z86viKwQjTpV0Q= -github.com/Azure/azure-event-hubs-go/v3 v3.6.0 h1:UXRi5KewXYoTiekVjrj0gyGfbyGvtbYdot6/4IMf4I4= -github.com/Azure/azure-event-hubs-go/v3 v3.6.0/go.mod h1:UgyRnRU7H5e33igaLHJTqbkoNR1uj0j3MA/n7dABU24= +github.com/Azure/azure-event-hubs-go/v3 v3.6.2 h1:7rNj1/iqS/i3mUKokA2n2eMYO72TB7lO7OmpbKoakKY= +github.com/Azure/azure-event-hubs-go/v3 v3.6.2/go.mod h1:n+ocYr9j2JCLYqUqz9eI+lx/TEAtL/g6rZzyTFSuIpc= github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-amqp v1.0.0 h1:QfCugi1M+4F2JDTRgVnRw7PYXLXZ9hmqk3+9+oJh3OA= @@ -16,7 +33,9 @@ github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3 github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk= github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U= github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 h1:iM6UAvjR97ZIeR93qTcwpKNMpV+/FTWjwEbuPD495Tk= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 h1:LXl088ZQlP0SBppGFsRZonW6hSvwgL5gRByMbvUbx8U= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= @@ -31,178 +50,202 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20230619160724-3fbb1f12458c h1:figwFwYep1Qnl64Y+Rc8tyQWE0xvYAN+5EX+rD40pTU= -github.com/ProtonMail/go-crypto v0.0.0-20230619160724-3fbb1f12458c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= -github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= -github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ= +github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/devigned/tab v0.1.1 h1:3mD6Kb1mUOYeLpJvTVSDwSg5ZsfSxfvxGRTxRsJsITA= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= -github.com/emicklei/go-restful/v3 v3.10.0 h1:X4gma4HM7hFm6WMeAsTfqA0GOfdNoCzBIkHGoRLGXuM= -github.com/emicklei/go-restful/v3 v3.10.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/docker/cli v24.0.9+incompatible h1:OxbimnP/z+qVjDLpq9wbeFU3Nc30XhSe+LkwYQisD50= +github.com/docker/cli v24.0.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M= +github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= +github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= -github.com/fluxcd/helm-controller/api v0.35.0 h1:UyhKXPni5z69DzPW7GtECGGdUwKsB+OTI0A/wc7HmFY= -github.com/fluxcd/helm-controller/api v0.35.0/go.mod h1:CdHMtr5wM0xgDt/PS147H7QQS+zDxAFgDW3ZN8MnUlU= -github.com/fluxcd/image-automation-controller/api v0.35.0 h1:B7yJdAbnx0hwI6ffVu0YAyc3d3sqjXrv6ySK++g3t28= -github.com/fluxcd/image-automation-controller/api v0.35.0/go.mod h1:67HbnrXDy550zJKZZbzAZ0yfzL0Ge1O18yn/shcgy0Y= -github.com/fluxcd/image-reflector-controller/api v0.29.1 h1:mnFaO60b8WyCxpc72utmAJ7J1ZtZjGGThH9NdxVeEyo= -github.com/fluxcd/image-reflector-controller/api v0.29.1/go.mod h1:aYwq15SpkAD/urQme2iEsNouTscfR1Qo39KBlJ+0EW8= -github.com/fluxcd/kustomize-controller/api v1.0.1 h1:zz9zx4Mc7rw9gqdgdhMWX1uDM2uR1x7WBUujKs4mdx8= -github.com/fluxcd/kustomize-controller/api v1.0.1/go.mod h1:rYUovoofr3bVPgQowWj/CSGw73qoH0tOCopJ3oNh7lM= -github.com/fluxcd/notification-controller/api v1.0.0 h1:WiXS116pwhFp7Qe/Fc1GmKCfLBRnZu+rYCeIn07cO+4= -github.com/fluxcd/notification-controller/api v1.0.0/go.mod h1:uamWZwmhSHE7xdJkgRhH4Eo5/SKKILFI4o+CoQjlczc= -github.com/fluxcd/pkg/apis/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q= -github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8= -github.com/fluxcd/pkg/apis/event v0.5.1 h1:UrEmKwTK/lt42gMZunl8BQBMzjf8PSqGbWDs/GB839c= -github.com/fluxcd/pkg/apis/event v0.5.1/go.mod h1:GzBAzS8bq7751wvNkaSnr3kuwFVuWTPL20D77UgSNJQ= -github.com/fluxcd/pkg/apis/kustomize v1.1.1 h1:MSGn4z0R9PptmoPFHnx2nEZ8Jtl1sKfw0cuDQY2HYwM= -github.com/fluxcd/pkg/apis/kustomize v1.1.1/go.mod h1:0pCu0ecIY+ZM0iE/hOHYwCMZ3b0SpBrjJ1SH3FFyYdE= -github.com/fluxcd/pkg/apis/meta v1.1.1 h1:sLAKLbEu7rRzJ+Mytffu3NcpfdbOBTa6hcpOQzFWm+M= -github.com/fluxcd/pkg/apis/meta v1.1.1/go.mod h1:soCfzjFWbm1mqybDcOywWKTCEYlH3skpoNGTboVk234= -github.com/fluxcd/pkg/git v0.12.3 h1:1KmRYTdcBKDUutg6NIT4x0BCCMT72PjjXs3AnHjybHY= -github.com/fluxcd/pkg/git v0.12.3/go.mod h1:ID2sry5OqYbgJxvANc7s6V/YwafnQd7e1AGoDvwztAU= -github.com/fluxcd/pkg/git/gogit v0.12.1 h1:06jzHOTntYN5xCSQvyFXtLXdqoP8crLh7VYgtXS9+wo= -github.com/fluxcd/pkg/git/gogit v0.12.1/go.mod h1:Z4Ysp8VifKTvWpjJMKncJsgb2iBqHuIeK80VGjlU41Y= -github.com/fluxcd/pkg/gittestserver v0.8.4 h1:rA/QUZnfH77ZZG+5xfMqjgEHJdzeeE6Nn1o8cops/bU= -github.com/fluxcd/pkg/ssh v0.8.0 h1:CqHIsWYfAtGxh2D6ZvzGTkFout6MaQnFpytPzJPbDLA= -github.com/fluxcd/pkg/ssh v0.8.0/go.mod h1:bo6HgWqIIuXU6r5HCxRFa7Uo7b4Nnzsz6MvdtAYn2XY= -github.com/fluxcd/pkg/version v0.2.2 h1:ZpVXECeLA5hIQMft11iLp6gN3cKcz6UNuVTQPw/bRdI= -github.com/fluxcd/pkg/version v0.2.2/go.mod h1:NGnh/no8S6PyfCDxRFrPY3T5BUnqP48MxfxNRU0z8C0= -github.com/fluxcd/source-controller/api v1.0.1 h1:nycylbNBnQd+EO4UHpqXqAQJ1cGAPxgBbrfERCQ1pp8= -github.com/fluxcd/source-controller/api v1.0.1/go.mod h1:rAY5FRFGZUKpIFNyYANHIgPgJPvbALBHWsq/zHw/cXQ= +github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= +github.com/fluxcd/helm-controller/api v1.0.1 h1:Gn9qEVuif6D5+gHmVwTEZkR4+nmLOcOhKx4Sw2gL2EA= +github.com/fluxcd/helm-controller/api v1.0.1/go.mod h1:/6AD5a2qjo/ttxVM8GR33syLZwqigta60DCLdy8GrME= +github.com/fluxcd/image-automation-controller/api v0.38.0 h1:+phX67uf0INGDC4sghsPPNUiE8taVp7AcWgJH8LkiUk= +github.com/fluxcd/image-automation-controller/api v0.38.0/go.mod h1:FfWWRxG03514+MUNJ+uN6fXzjwdbqsJqCggukIZ1tx8= +github.com/fluxcd/image-reflector-controller/api v0.32.0 h1:mb/v9JzRHcjLcnGqmgsq0+yCcoOyae/TrOWae9T87PE= +github.com/fluxcd/image-reflector-controller/api v0.32.0/go.mod h1:Ap3/KK8MfQAdmuhakg9CweEa3Xwwmvausbqrgd3HBWY= +github.com/fluxcd/kustomize-controller/api v1.3.0 h1:IwXkU48lQ/YhU6XULlPXDgQlnpNyQdCNbUvhLdWVIbE= +github.com/fluxcd/kustomize-controller/api v1.3.0/go.mod h1:kg/WM9Uye5NOqGVW/F3jnkjrlgFZHHa84+4lnzOV8fI= +github.com/fluxcd/notification-controller/api v1.3.0 h1:e3Plvo44XIKP2pUjwx8U4/fMpPwVM3EvJrYLIIqcVrI= +github.com/fluxcd/notification-controller/api v1.3.0/go.mod h1:KUaWXACNwWpAYo/Q4mzBjGbsYlUzXdq654jc1XpgMQw= +github.com/fluxcd/pkg/apis/acl v0.3.0 h1:UOrKkBTOJK+OlZX7n8rWt2rdBmDCoTK+f5TY2LcZi8A= +github.com/fluxcd/pkg/apis/acl v0.3.0/go.mod h1:WVF9XjSMVBZuU+HTTiSebGAWMgM7IYexFLyVWbK9bNY= +github.com/fluxcd/pkg/apis/event v0.9.0 h1:iKxU+3v/3bAuC1C1iXg1mjbIiaEQet7WETh8lsfdcpY= +github.com/fluxcd/pkg/apis/event v0.9.0/go.mod h1:5LjcTeppPMEyOgtTbIP7q2GbVwIRUfujIxynIjHBV/k= +github.com/fluxcd/pkg/apis/kustomize v1.5.0 h1:ah4sfqccnio+/5Edz/tVz6LetFhiBoDzXAElj6fFCzU= +github.com/fluxcd/pkg/apis/kustomize v1.5.0/go.mod h1:nEzhnhHafhWOUUV8VMFLojUOH+HHDEsL75y54mt/c30= +github.com/fluxcd/pkg/apis/meta v1.5.0 h1:/G82d2Az5D9op3F+wJUpD8jw/eTV0suM6P7+cSURoUM= +github.com/fluxcd/pkg/apis/meta v1.5.0/go.mod h1:Y3u7JomuuKtr5fvP1Iji2/50FdRe5GcBug2jawNVkdM= +github.com/fluxcd/pkg/git v0.19.0 h1:zIv+GAT0ieIUpnGBVi3Bhax/qq4Rr28BW7Jv4DTt6zE= +github.com/fluxcd/pkg/git v0.19.0/go.mod h1:wkqUOSrTjtsVVk/gC6/7RxVpi9GcqAA+7O5HVJF5S14= +github.com/fluxcd/pkg/git/gogit v0.19.0 h1:SdoNAmC/HTPXniQjp609X59rCsBiA+Sdq1Hv8SnYC6I= +github.com/fluxcd/pkg/git/gogit v0.19.0/go.mod h1:8kOmrNMjq8daQTVLhp6klhuoY8+s81gydM0MozDjaHM= +github.com/fluxcd/pkg/gittestserver v0.12.0 h1:QGbIVyje9U6urSAeDw3diKb/5wdA+Cnw1YJN+3Zflaw= +github.com/fluxcd/pkg/gittestserver v0.12.0/go.mod h1:Eh82e+kzKdhpafnUwR5oCBmxqAqhF5QuCn290AFntPM= +github.com/fluxcd/pkg/runtime v0.47.1 h1:Q1tAFsp92uurWyoEe52AmMC4k+6DYTPBrUQDs+nz/9c= +github.com/fluxcd/pkg/runtime v0.47.1/go.mod h1:97a+PqpWMgQsoqh91uH3EQz+/DC7Uxc8xcu/rDHFC5c= +github.com/fluxcd/pkg/ssh v0.13.0 h1:lPU1Gst8XIz7AU2dhdqVFaaOWd54/O1LZu62vH4JB/s= +github.com/fluxcd/pkg/ssh v0.13.0/go.mod h1:J9eyirMd4s++tWG4euRRhmcthKX203GPHpzFpH++TP8= +github.com/fluxcd/pkg/version v0.4.0 h1:3F6oeIZ+ug/f7pALIBhcUhfURel37EPPOn7nsGfsnOg= +github.com/fluxcd/pkg/version v0.4.0/go.mod h1:izVsSDxac81qWRmpOL9qcxZYx+zAN1ajoP5SidGP6PA= +github.com/fluxcd/source-controller/api v1.3.0 h1:Z5Lq0aJY87yg0cQDEuwGLKS60GhdErCHtsi546HUt10= +github.com/fluxcd/source-controller/api v1.3.0/go.mod h1:+tfd0vltjcVs/bbnq9AlYR9AAHSVfM/Z4v4TpQmdJf4= +github.com/fluxcd/test-infra/tftestenv v0.0.0-20240429114247-01ecf2cc78e4 h1:Oe1h6fi+571n0HLw5u5nnaWBn3Yja1/7DKnHAxb10SE= +github.com/fluxcd/test-infra/tftestenv v0.0.0-20240429114247-01ecf2cc78e4/go.mod h1:liFlLEXgambGVdWSJ4JzbIHf1Vjpp1HwUyPazPIVZug= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= -github.com/go-git/go-git/v5 v5.7.1-0.20230702134234-dd4e2b7f4b01 h1:+yTyIn80JO1XGuLjgnUwnJDFpRw5dOVk6Yv5dAzi5dI= -github.com/go-git/go-git/v5 v5.7.1-0.20230702134234-dd4e2b7f4b01/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= -github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= -github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= -github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= +github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.5.2 h1:SfwMFnEXVVirpwkDuSF5kymUOhrUxrTq3udEseZdOD0= -github.com/hashicorp/hc-install v0.5.2/go.mod h1:9QISwe6newMWIfEiXpzuu1k9HAGtQYgnSH8H9T8wmoI= -github.com/hashicorp/terraform-exec v0.18.1 h1:LAbfDvNQU1l0NOQlTuudjczVhHj061fNX5H8XZxHlH4= -github.com/hashicorp/terraform-exec v0.18.1/go.mod h1:58wg4IeuAJ6LVsLUeD2DWZZoc/bYi6dzhLHzxM41980= -github.com/hashicorp/terraform-json v0.15.0 h1:/gIyNtR6SFw6h5yzlbDbACyGvIhKtQi8mTsbkNd79lE= -github.com/hashicorp/terraform-json v0.15.0/go.mod h1:+L1RNzjDU5leLFZkHTFTbJXaoqUC6TqXlFgDoOXrtvk= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/hashicorp/hc-install v0.6.2 h1:V1k+Vraqz4olgZ9UzKiAcbman9i9scg9GgSt/U3mw/M= +github.com/hashicorp/hc-install v0.6.2/go.mod h1:2JBpd+NCFKiHiu/yYCGaPyPHhZLxXTpz8oreHa/a3Ps= +github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8JyYF3vpnuEo= +github.com/hashicorp/terraform-exec v0.20.0/go.mod h1:ckKGkJWbsNqFKV1itgMnE0hY9IYf1HoiekpuN0eWoDw= +github.com/hashicorp/terraform-json v0.21.0 h1:9NQxbLNqPbEMze+S6+YluEdXgJmhQykRyRNd+zTI05U= +github.com/hashicorp/terraform-json v0.21.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= @@ -213,27 +256,23 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 h1:YH424zrwLTlyHSH/GzLMJeu5zhYVZSx5RQxGKm1h96s= github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5/go.mod h1:PoGiBqKSQK1vIfQ+yVaFcGjDySHvym6FM1cNYnwzbrY= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -241,69 +280,77 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= +github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= +github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= +github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= -github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ= +github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= -github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= +github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= +github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= -github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA= +github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +go.einride.tech/aip v0.67.1 h1:d/4TW92OxXBngkSOwWS2CH5rez869KpKMaN44mdxkFI= +go.einride.tech/aip v0.67.1/go.mod h1:ZGX4/zKw8dcgzdLsrvpOOGxfxI2QSk12SlP7d6c0/XI= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -312,9 +359,11 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -322,85 +371,74 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -411,30 +449,32 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= +google.golang.org/api v0.177.0 h1:8a0p/BbPa65GlqGWtUKxot4p0TV8OGOfyTjtmkXNXmk= +google.golang.org/api v0.177.0/go.mod h1:srbhue4MLjkjbkux5p3dw/ocYOSZTaIEvf7bCOnFQDw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= +google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 h1:DTJM0R8LECCgFeUwApvcEJHz85HLagW8uRENYxHh1ww= +google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6/go.mod h1:10yRODfgim2/T8csjQsMPgZOMvtytXKTDRzH6HRGzRw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -444,13 +484,9 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= +google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -459,33 +495,35 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.27.3 h1:yR6oQXXnUEBWEWcvPWS0jQL575KoAboQPfJAuKNrw5Y= -k8s.io/api v0.27.3/go.mod h1:C4BNvZnQOF7JA/0Xed2S+aUyJSfTGkGFxLXz9MnpIpg= -k8s.io/apiextensions-apiserver v0.27.3 h1:xAwC1iYabi+TDfpRhxh4Eapl14Hs2OftM2DN5MpgKX4= -k8s.io/apiextensions-apiserver v0.27.3/go.mod h1:BH3wJ5NsB9XE1w+R6SSVpKmYNyIiyIz9xAmBl8Mb+84= -k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= -k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= -k8s.io/client-go v0.27.3 h1:7dnEGHZEJld3lYwxvLl7WoehK6lAq7GvgjxpA3nv1E8= -k8s.io/client-go v0.27.3/go.mod h1:2MBEKuTo6V1lbKy3z1euEGnhPfGZLKTS9tiJ2xodM48= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= -k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= -k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= -sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= +k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= +k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= +k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= +k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= +k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= +k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= +k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 h1:SbdLaI6mM6ffDSJCadEaD4IkuPzepLDGlkd2xV0t1uA= +k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY= +k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.18.1 h1:RpWbigmuiylbxOCLy0tGnq1cU1qWPwNIQzoJk+QeJx4= +sigs.k8s.io/controller-runtime v0.18.1/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/tests/integration/image_repo_test.go b/tests/integration/image_repo_test.go new file mode 100644 index 0000000000..0abee9c7d7 --- /dev/null +++ b/tests/integration/image_repo_test.go @@ -0,0 +1,192 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + "time" + + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + automationv1 "github.com/fluxcd/image-automation-controller/api/v1beta2" + reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1" +) + +func TestImageRepositoryAndAutomation(t *testing.T) { + g := NewWithT(t) + ctx := context.TODO() + branchName := "image-repository" + testID := branchName + "-" + randStringRunes(5) + imageURL := fmt.Sprintf("%s/podinfo", cfg.testRegistry) + + manifest := fmt.Sprintf(`apiVersion: apps/v1 +kind: Deployment +metadata: + name: podinfo + namespace: %[1]s +spec: + selector: + matchLabels: + app: podinfo + template: + metadata: + labels: + app: podinfo + spec: + containers: + - name: podinfod + image: %[2]s:%[3]s # {"$imagepolicy": "%[1]s:podinfo"} + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 +`, testID, imageURL, oldPodinfoVersion) + + repoUrl := getTransportURL(cfg.applicationRepository) + client, err := getRepository(ctx, t.TempDir(), repoUrl, defaultBranch, cfg.defaultAuthOpts) + g.Expect(err).ToNot(HaveOccurred()) + files := make(map[string]io.Reader) + files[testID+"/podinfo.yaml"] = strings.NewReader(manifest) + + err = commitAndPushAll(ctx, client, files, branchName) + g.Expect(err).ToNot(HaveOccurred()) + + err = setUpFluxConfig(ctx, testID, nsConfig{ + repoURL: repoUrl, + path: testID, + ref: &sourcev1.GitRepositoryRef{ + Branch: branchName, + }, + }) + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + err := tearDownFluxConfig(ctx, testID) + if err != nil { + t.Logf("failed to delete resources in '%s' namespace: %s", testID, err) + } + }) + + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID) + if err != nil { + return false + } + return true + }, testTimeout, testInterval).Should(BeTrue()) + + imageRepository := reflectorv1.ImageRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podinfo", + Namespace: testID, + }, + Spec: reflectorv1.ImageRepositorySpec{ + Image: imageURL, + Interval: metav1.Duration{ + Duration: 1 * time.Minute, + }, + Provider: infraOpts.Provider, + }, + } + g.Expect(testEnv.Create(ctx, &imageRepository)).To(Succeed()) + defer testEnv.Delete(ctx, &imageRepository) + + imagePolicy := reflectorv1.ImagePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podinfo", + Namespace: testID, + }, + Spec: reflectorv1.ImagePolicySpec{ + ImageRepositoryRef: meta.NamespacedObjectReference{ + Name: imageRepository.Name, + }, + Policy: reflectorv1.ImagePolicyChoice{ + SemVer: &reflectorv1.SemVerPolicy{ + Range: "6.0.x", + }, + }, + }, + } + g.Expect(testEnv.Create(ctx, &imagePolicy)).To(Succeed()) + defer testEnv.Delete(ctx, &imagePolicy) + + imageAutomation := automationv1.ImageUpdateAutomation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podinfo", + Namespace: testID, + }, + Spec: automationv1.ImageUpdateAutomationSpec{ + Interval: metav1.Duration{ + Duration: 1 * time.Minute, + }, + SourceRef: automationv1.CrossNamespaceSourceReference{ + Kind: "GitRepository", + Name: testID, + }, + GitSpec: &automationv1.GitSpec{ + Checkout: &automationv1.GitCheckoutSpec{ + Reference: sourcev1.GitRepositoryRef{ + Branch: branchName, + }, + }, + Commit: automationv1.CommitSpec{ + Author: automationv1.CommitUser{ + Email: "imageautomation@example.com", + Name: "imageautomation", + }, + }, + }, + Update: &automationv1.UpdateStrategy{ + Path: testID, + Strategy: automationv1.UpdateStrategySetters, + }, + }, + } + g.Expect(testEnv.Create(ctx, &imageAutomation)).To(Succeed()) + defer testEnv.Delete(ctx, &imageAutomation) + + // Wait for image repository to be ready + g.Eventually(func() bool { + client, err := getRepository(ctx, t.TempDir(), repoUrl, branchName, cfg.defaultAuthOpts) + if err != nil { + return false + } + + b, err := os.ReadFile(filepath.Join(client.Path(), testID, "podinfo.yaml")) + if err != nil { + return false + } + if bytes.Contains(b, []byte(newPodinfoVersion)) == false { + return false + } + return true + }, testTimeout, testInterval).Should(BeTrue()) +} diff --git a/tests/integration/notification_test.go b/tests/integration/notification_test.go new file mode 100644 index 0000000000..ef154147fb --- /dev/null +++ b/tests/integration/notification_test.go @@ -0,0 +1,177 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "context" + "encoding/json" + "io" + "strings" + "testing" + "time" + + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + notiv1 "github.com/fluxcd/notification-controller/api/v1" + notiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3" + events "github.com/fluxcd/pkg/apis/event/v1beta1" + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1" +) + +func TestNotification(t *testing.T) { + g := NewWithT(t) + + ctx := context.TODO() + branchName := "test-notification" + testID := branchName + "-" + randStringRunes(5) + defer cfg.notificationCfg.closeChan() + + // Setup Flux resources + manifest := `apiVersion: v1 +kind: ConfigMap +metadata: + name: foobar` + repoUrl := getTransportURL(cfg.applicationRepository) + client, err := getRepository(ctx, t.TempDir(), repoUrl, defaultBranch, cfg.defaultAuthOpts) + g.Expect(err).ToNot(HaveOccurred()) + files := make(map[string]io.Reader) + files["configmap.yaml"] = strings.NewReader(manifest) + err = commitAndPushAll(ctx, client, files, branchName) + g.Expect(err).ToNot(HaveOccurred()) + + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + }, + } + g.Expect(testEnv.Create(ctx, &namespace)).To(Succeed()) + defer testEnv.Delete(ctx, &namespace) + + provider := notiv1beta3.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + Namespace: testID, + }, + Spec: notiv1beta3.ProviderSpec{ + Type: cfg.notificationCfg.providerType, + Address: cfg.notificationCfg.providerAddress, + Channel: cfg.notificationCfg.providerChannel, + }, + } + + if cfg.notificationCfg.secret != nil { + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + Namespace: testID, + }, + StringData: cfg.notificationCfg.secret, + } + + g.Expect(testEnv.Create(ctx, &secret)).To(Succeed()) + defer testEnv.Delete(ctx, &secret) + + provider.Spec.SecretRef = &meta.LocalObjectReference{ + Name: testID, + } + } + + g.Expect(testEnv.Create(ctx, &provider)).To(Succeed()) + defer testEnv.Delete(ctx, &provider) + + alert := notiv1beta3.Alert{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + Namespace: testID, + }, + Spec: notiv1beta3.AlertSpec{ + ProviderRef: meta.LocalObjectReference{ + Name: provider.Name, + }, + EventSources: []notiv1.CrossNamespaceObjectReference{ + { + Kind: "Kustomization", + Name: testID, + Namespace: testID, + }, + }, + }, + } + g.Expect(testEnv.Create(ctx, &alert)).ToNot(HaveOccurred()) + defer testEnv.Delete(ctx, &alert) + + modifyKsSpec := func(spec *kustomizev1.KustomizationSpec) { + spec.Interval = metav1.Duration{Duration: 30 * time.Second} + spec.HealthChecks = []meta.NamespacedObjectKindReference{ + { + APIVersion: "v1", + Kind: "ConfigMap", + Name: "foobar", + Namespace: testID, + }, + } + } + g.Expect(setUpFluxConfig(ctx, testID, nsConfig{ + repoURL: repoUrl, + ref: &sourcev1.GitRepositoryRef{ + Branch: branchName, + }, + path: "./", + modifyKsSpec: modifyKsSpec, + })).To(Succeed()) + t.Cleanup(func() { + err := tearDownFluxConfig(ctx, testID) + if err != nil { + t.Logf("failed to delete resources in '%s' namespace: %s", testID, err) + } + }) + + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv, testID, testID) + if err != nil { + t.Log(err) + return false + } + return true + }, testTimeout, testInterval).Should(BeTrue()) + + // Wait to read event from notification channel. + g.Eventually(func() bool { + select { + case eventJson := <-cfg.notificationCfg.notificationChan: + event := &events.Event{} + err := json.Unmarshal([]byte(eventJson), event) + if err != nil { + t.Logf("the received event type does not match Flux format, error: %v", err) + return false + } + + if event.InvolvedObject.Kind == kustomizev1.KustomizationKind && + event.InvolvedObject.Name == testID && event.InvolvedObject.Namespace == testID { + return true + } + + return false + default: + return false + } + }, testTimeout, 1*time.Second).Should(BeTrue()) +} diff --git a/tests/integration/oci_test.go b/tests/integration/oci_test.go new file mode 100644 index 0000000000..cfacbfc062 --- /dev/null +++ b/tests/integration/oci_test.go @@ -0,0 +1,122 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "context" + "fmt" + "testing" + "time" + + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + helmv2 "github.com/fluxcd/helm-controller/api/v2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" +) + +func TestOCIHelmRelease(t *testing.T) { + g := NewWithT(t) + ctx := context.TODO() + + // Create namespace for test + testID := "oci-helm-" + randStringRunes(5) + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + }, + } + g.Expect(testEnv.Create(ctx, &namespace)).To(Succeed()) + defer testEnv.Delete(ctx, &namespace) + + repoURL := fmt.Sprintf("%s/charts/podinfo", cfg.testRegistry) + err := pushImagesFromURL(repoURL, "ghcr.io/stefanprodan/charts/podinfo:6.2.0", []string{"6.2.0"}) + g.Expect(err).ToNot(HaveOccurred()) + + // Create HelmRepository. + helmRepository := sourcev1.HelmRepository{ + ObjectMeta: metav1.ObjectMeta{Name: testID, Namespace: testID}, + Spec: sourcev1.HelmRepositorySpec{ + URL: fmt.Sprintf("oci://%s", cfg.testRegistry), + Interval: metav1.Duration{ + Duration: 5 * time.Minute, + }, + Provider: infraOpts.Provider, + PassCredentials: true, + Type: "oci", + }, + } + + g.Expect(testEnv.Create(ctx, &helmRepository)).To(Succeed()) + defer testEnv.Delete(ctx, &helmRepository) + + // create helm release + helmRelease := helmv2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{Name: testID, Namespace: testID}, + Spec: helmv2.HelmReleaseSpec{ + Chart: &helmv2.HelmChartTemplate{ + Spec: helmv2.HelmChartTemplateSpec{ + Interval: &metav1.Duration{ + Duration: 10 * time.Minute, + }, + Chart: "charts/podinfo", + Version: "6.2.0", + SourceRef: helmv2.CrossNamespaceObjectReference{ + Kind: sourcev1.HelmRepositoryKind, + Name: helmRepository.Name, + Namespace: helmRepository.Namespace, + }, + }, + }, + }, + } + + g.Expect(testEnv.Create(ctx, &helmRelease)).To(Succeed()) + defer testEnv.Delete(ctx, &helmRelease) + + g.Eventually(func() bool { + chart := &sourcev1.HelmChart{} + nn := types.NamespacedName{ + Name: fmt.Sprintf("%s-%s", helmRelease.Name, helmRelease.Namespace), + Namespace: helmRelease.Namespace, + } + if err := testEnv.Get(ctx, nn, chart); err != nil { + t.Logf("error getting helm chart %s\n", err.Error()) + return false + } + if err := checkReadyCondition(chart); err != nil { + t.Log(err) + return false + } + + obj := &helmv2.HelmRelease{} + nn = types.NamespacedName{Name: helmRelease.Name, Namespace: helmRelease.Namespace} + if err := testEnv.Get(ctx, nn, obj); err != nil { + t.Logf("error getting helm release %s\n", err.Error()) + return false + } + + if err := checkReadyCondition(obj); err != nil { + t.Log(err) + return false + } + + return true + }, testTimeout, testInterval).Should(BeTrue()) +} diff --git a/tests/integration/sops_encryption_test.go b/tests/integration/sops_encryption_test.go new file mode 100644 index 0000000000..053c7ed16e --- /dev/null +++ b/tests/integration/sops_encryption_test.go @@ -0,0 +1,138 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "context" + "fmt" + "io" + "log" + "os" + "testing" + + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + "github.com/fluxcd/test-infra/tftestenv" +) + +func TestKeyVaultSops(t *testing.T) { + g := NewWithT(t) + ctx := context.TODO() + branchName := "key-vault" + testID := branchName + "-" + randStringRunes(5) + secretYaml := `apiVersion: v1 +kind: Secret +metadata: + name: "test" +stringData: + foo: "bar"` + + repoUrl := getTransportURL(cfg.applicationRepository) + tmpDir := t.TempDir() + client, err := getRepository(ctx, tmpDir, repoUrl, defaultBranch, cfg.defaultAuthOpts) + g.Expect(err).ToNot(HaveOccurred()) + + dir := client.Path() + "/key-vault-sops" + g.Expect(os.Mkdir(dir, 0o700)).To(Succeed()) + + filename := dir + "secret.enc.yaml" + f, err := os.Create(filename) + g.Expect(err).ToNot(HaveOccurred()) + defer f.Close() + + _, err = f.Write([]byte(secretYaml)) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(f.Sync()).To(Succeed()) + + err = tftestenv.RunCommand(ctx, client.Path(), + fmt.Sprintf("sops --encrypt --encrypted-regex '^(data|stringData)$' %s --in-place %s", cfg.sopsArgs, filename), + tftestenv.RunCommandOptions{}) + g.Expect(err).ToNot(HaveOccurred()) + + r, err := os.Open(filename) + g.Expect(err).ToNot(HaveOccurred()) + + files := make(map[string]io.Reader) + files["key-vault-sops/secret.enc.yaml"] = r + err = commitAndPushAll(ctx, client, files, branchName) + g.Expect(err).ToNot(HaveOccurred()) + + modifyKsSpec := func(spec *kustomizev1.KustomizationSpec) { + spec.Decryption = &kustomizev1.Decryption{ + Provider: "sops", + } + if cfg.sopsSecretData != nil { + spec.Decryption.SecretRef = &meta.LocalObjectReference{ + Name: "sops-keys", + } + } + } + + err = setUpFluxConfig(ctx, testID, nsConfig{ + ref: &sourcev1.GitRepositoryRef{ + Branch: branchName, + }, + repoURL: repoUrl, + path: "./key-vault-sops", + modifyKsSpec: modifyKsSpec, + protocol: cfg.defaultGitTransport, + }) + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + err := tearDownFluxConfig(ctx, testID) + if err != nil { + log.Printf("failed to delete resources in '%s' namespace", testID) + } + }) + + if cfg.sopsSecretData != nil { + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sops-keys", + Namespace: testID, + }, + StringData: cfg.sopsSecretData, + } + g.Expect(testEnv.Create(ctx, &secret)).To(Succeed()) + defer testEnv.Delete(ctx, &secret) + } + + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID) + if err != nil { + return false + } + nn := types.NamespacedName{Name: "test", Namespace: testID} + secret := &corev1.Secret{} + err = testEnv.Get(ctx, nn, secret) + if err != nil { + return false + } + + if string(secret.Data["foo"]) == "bar" { + return true + } + + return false + }, testTimeout, testInterval).Should(BeTrue()) +} diff --git a/tests/integration/suite_test.go b/tests/integration/suite_test.go new file mode 100644 index 0000000000..0b55c4c2a6 --- /dev/null +++ b/tests/integration/suite_test.go @@ -0,0 +1,348 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "context" + "flag" + "fmt" + "log" + "math/rand" + "os" + "testing" + "time" + + "github.com/hashicorp/terraform-exec/tfexec" + tfjson "github.com/hashicorp/terraform-json" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/kubernetes/scheme" + + helmv2 "github.com/fluxcd/helm-controller/api/v2" + automationv1 "github.com/fluxcd/image-automation-controller/api/v1beta2" + reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + notiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3" + "github.com/fluxcd/pkg/git" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" + "github.com/fluxcd/test-infra/tftestenv" +) + +const ( + // azureTerraformPath is the path to the folder containing the + // terraform files for azure infra + azureTerraformPath = "./terraform/azure" + // gcpTerraformPath is the path to the folder containing the + // terraform files for gcp infra + gcpTerraformPath = "./terraform/gcp" + + // kubeconfigPath is the path of the file containing the kubeconfig + kubeconfigPath = "./build/kubeconfig" + + // fluxBin is the path to the flux binary. + fluxBin = "./build/flux" + + // default branch to be used when cloning git repositories + defaultBranch = "main" + + // envVarGitRepoSSHPath is the environment variable that contains the path + // to the ssh key for the git repository + envVarGitRepoSSHPath = "GITREPO_SSH_PATH" + // envVarGitRepoSSHPubPath is the environment variable that contains the path + // to the ssh public key for the git repository + envVarGitRepoSSHPubPath = "GITREPO_SSH_PUB_PATH" +) + +var ( + // supportedProviders are the providers supported by the test. + supportedProviders = []string{"azure", "gcp"} + + // cfg is a struct containing different variables needed for the test. + cfg *testConfig + + // infraOpts are the options for running the terraform environment + infraOpts tftestenv.Options + + // versions to tag and push for the podinfo image + oldPodinfoVersion = "6.0.0" + newPodinfoVersion = "6.0.1" + podinfoTags = []string{oldPodinfoVersion, newPodinfoVersion} + + // testEnv is the test environment. It contains test infrastructure and + // kubernetes client of the created cluster. + testEnv *tftestenv.Environment + + // testTimeout is used as a timeout when testing a condition with gomega's eventually + testTimeout = 60 * time.Second + // testInterval is used as an interval when testing a condition with gomega's eventually + testInterval = 5 * time.Second + + random *rand.Rand + + letterRunes = []rune("abcdefghijklmnopqrstuvwxyz1234567890") + + localImg = "ghcr.io/stefanprodan/podinfo" +) + +// testConfig hold different variable that will be needed by the different test functions. +type testConfig struct { + // authentication info for git repositories + gitPat string + gitUsername string + gitPrivateKey string + gitPublicKey string + defaultGitTransport git.TransportType + defaultAuthOpts *git.AuthOptions + knownHosts string + fleetInfraRepository gitUrl + applicationRepository gitUrl + + // sopsArgs is the cloud provider dependent argument to pass to the sops cli + sopsArgs string + + // notificationCfg contains the values needed to properly set up notification on the + // cluster. + notificationCfg notificationConfig + + // sopsSecretData is the secret's data for the sops decryption + sopsSecretData map[string]string + // kustomizationYaml is the content of the kustomization.yaml for customizing the Flux manifests + kustomizationYaml string + + // testRegistry is the registry of the cloud provider. + testRegistry string +} + +// notificationConfig contains various fields for configuring +// providers and testing notifications for the different +// cloud providers. +type notificationConfig struct { + providerChannel string + providerType string + providerAddress string + secret map[string]string + notificationChan chan []byte + closeChan func() +} + +// gitUrl contains the http/ssh urls for the created git repositories +// on the various cloud providers. +type gitUrl struct { + http string + ssh string +} + +// getTestConfig gets the test configuration that contains different variables for running the tests +type getTestConfig func(ctx context.Context, output map[string]*tfjson.StateOutput) (*testConfig, error) + +// registryLoginFunc is used to perform registry login against a provider based +// on the terraform state output values. It returns the test registry +// to test against, read from the terraform state output. +type registryLoginFunc func(ctx context.Context, output map[string]*tfjson.StateOutput) (string, error) + +// providerConfig contains the test configurations for the different cloud providers +type providerConfig struct { + terraformPath string + createKubeconfig tftestenv.CreateKubeconfig + getTestConfig getTestConfig + // registryLogin is used to perform registry login. + registryLogin registryLoginFunc +} + +func init() { + utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme)) + utilruntime.Must(sourcev1beta2.AddToScheme(scheme.Scheme)) + utilruntime.Must(kustomizev1.AddToScheme(scheme.Scheme)) + utilruntime.Must(helmv2.AddToScheme(scheme.Scheme)) + utilruntime.Must(reflectorv1.AddToScheme(scheme.Scheme)) + utilruntime.Must(automationv1.AddToScheme(scheme.Scheme)) + utilruntime.Must(notiv1beta3.AddToScheme(scheme.Scheme)) + + random = rand.New(rand.NewSource(time.Now().UnixNano())) +} + +func TestMain(m *testing.M) { + ctx := context.TODO() + + infraOpts.Bindflags(flag.CommandLine) + flag.Parse() + + // Validate the provider. + if infraOpts.Provider == "" { + log.Fatalf("-provider flag must be set to one of %v", supportedProviders) + } + var supported bool + for _, p := range supportedProviders { + if p == infraOpts.Provider { + supported = true + break + } + } + if !supported { + log.Fatalf("Unsupported provider %q, must be one of %v", infraOpts.Provider, supportedProviders) + } + // get provider specific configuration + providerCfg := getProviderConfig(infraOpts.Provider) + if providerCfg == nil { + log.Fatalf("Failed to get provider config for %q", infraOpts.Provider) + } + + // Run destroy-only mode if enabled. + if infraOpts.DestroyOnly { + log.Println("Running in destroy-only mode...") + envOpts := []tftestenv.EnvironmentOption{ + tftestenv.WithVerbose(infraOpts.Verbose), + // Ignore any state lock in destroy-only mode. + tftestenv.WithTfDestroyOptions(tfexec.Lock(false)), + } + if err := tftestenv.Destroy(ctx, providerCfg.terraformPath, envOpts...); err != nil { + panic(err) + } + os.Exit(0) + } + + // Initialize with non-zero exit code to indicate failure by default unless + // set by a successful test run. + exitCode := 1 + + // Setup Terraform binary and init state + log.Printf("Setting up %s e2e test infrastructure", infraOpts.Provider) + envOpts := []tftestenv.EnvironmentOption{ + tftestenv.WithExisting(infraOpts.Existing), + tftestenv.WithRetain(infraOpts.Retain), + tftestenv.WithVerbose(infraOpts.Verbose), + tftestenv.WithCreateKubeconfig(providerCfg.createKubeconfig), + } + + // Create terraform infrastructure + var err error + testEnv, err = tftestenv.New(ctx, scheme.Scheme, providerCfg.terraformPath, kubeconfigPath, envOpts...) + if err != nil { + log.Fatalf("Failed to provision the test infrastructure: %v", err) + } + + defer func() { + if err := testEnv.Stop(ctx); err != nil { + log.Printf("Failed to stop environment: %v", err) + exitCode = 1 + } + + // Log the panic error before exit to surface the cause of panic. + if err := recover(); err != nil { + log.Printf("panic: %v", err) + } + os.Exit(exitCode) + }() + + // get terrraform infrastructure + outputs, err := testEnv.StateOutput(ctx) + if err != nil { + panic(fmt.Sprintf("Failed to get the terraform state output: %v", err)) + } + + // get provider specific test configuration + cfg, err = providerCfg.getTestConfig(ctx, outputs) + if err != nil { + panic(fmt.Sprintf("Failed to get test config: %v", err)) + } + + regUrl, err := providerCfg.registryLogin(ctx, outputs) + if err != nil { + panic(fmt.Sprintf("Failed to log into registry: %v", err)) + } + + cfg.testRegistry = regUrl + err = pushTestImages(ctx, cfg.testRegistry, podinfoTags) + if err != nil { + panic(fmt.Sprintf("Failed to push test images: %v", err)) + } + + tmpDir, err := os.MkdirTemp("", "*-flux-test") + if err != nil { + panic(fmt.Sprintf("Failed to create tmp dir: %v", err)) + } + defer func() { + err := os.RemoveAll(tmpDir) + if err != nil { + log.Printf("error removing tmp dir: %s\n", err) + } + }() + + log.Println("Installing flux") + err = installFlux(ctx, tmpDir, kubeconfigPath) + defer func() { + log.Println("Uninstalling Flux") + if err := uninstallFlux(ctx); err != nil { + log.Printf("Failed to uninstall: %v", err) + } + }() + if err != nil { + panic(fmt.Sprintf("error installing Flux: %v", err)) + } + + // On check failure, log and continue. Controllers may be ready by the time + // tests run. + log.Println("Running flux check") + if err := runFluxCheck(ctx); err != nil { + log.Printf("flux check failed: %v\n", err) + } + + log.Println("Running e2e tests") + exitCode = m.Run() +} + +func getProviderConfig(provider string) *providerConfig { + switch provider { + case "azure": + return &providerConfig{ + terraformPath: azureTerraformPath, + createKubeconfig: createKubeConfigAKS, + getTestConfig: getTestConfigAKS, + registryLogin: registryLoginACR, + } + case "gcp": + return &providerConfig{ + terraformPath: gcpTerraformPath, + createKubeconfig: createKubeConfigGKE, + getTestConfig: getTestConfigGKE, + registryLogin: registryLoginGCR, + } + } + return nil +} + +// pushTestImages pushes the local podinfo image to the remote repository specified +// by repoURL. The image should be existing on the machine. +func pushTestImages(ctx context.Context, repoURL string, tags []string) error { + for _, tag := range tags { + remoteImg := fmt.Sprintf("%s/podinfo:%s", repoURL, tag) + err := tftestenv.RetagAndPush(ctx, fmt.Sprintf("%s:%s", localImg, tag), remoteImg) + if err != nil { + return err + } + + } + return nil +} + +func randStringRunes(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[random.Intn(len(letterRunes))] + } + return string(b) +} diff --git a/tests/integration/terraform/azure/aks.tf b/tests/integration/terraform/azure/aks.tf new file mode 100644 index 0000000000..8ffadfce79 --- /dev/null +++ b/tests/integration/terraform/azure/aks.tf @@ -0,0 +1,20 @@ +module "aks" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/azure/aks" + + name = local.name + location = var.azure_location + tags = var.tags +} + +module "acr" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/azure/acr" + + name = local.name + location = var.azure_location + aks_principal_id = [module.aks.principal_id] + resource_group = module.aks.resource_group + admin_enabled = true + tags = var.tags + + depends_on = [module.aks] +} diff --git a/tests/integration/terraform/azure/azuredevops.tf b/tests/integration/terraform/azure/azuredevops.tf new file mode 100644 index 0000000000..f8a1a9b8a7 --- /dev/null +++ b/tests/integration/terraform/azure/azuredevops.tf @@ -0,0 +1,26 @@ +resource "azuredevops_project" "e2e" { + name = local.name + visibility = "private" + version_control = "Git" + work_item_template = "Agile" + description = "Test Project for Flux E2E test - Managed by Terraform" +} + + +resource "azuredevops_git_repository" "fleet_infra" { + project_id = azuredevops_project.e2e.id + name = "fleet-infra-${local.name}" + default_branch = "refs/heads/main" + initialization { + init_type = "Clean" + } +} + +resource "azuredevops_git_repository" "application" { + project_id = azuredevops_project.e2e.id + name = "application-${local.name}" + default_branch = "refs/heads/main" + initialization { + init_type = "Clean" + } +} diff --git a/tests/azure/terraform/aks/event-hub.tf b/tests/integration/terraform/azure/event-hub.tf similarity index 56% rename from tests/azure/terraform/aks/event-hub.tf rename to tests/integration/terraform/azure/event-hub.tf index 13162d2911..cda9500240 100644 --- a/tests/azure/terraform/aks/event-hub.tf +++ b/tests/integration/terraform/azure/event-hub.tf @@ -1,23 +1,24 @@ resource "azurerm_eventhub_namespace" "this" { - name = "ehns-${local.name_suffix}" - location = azurerm_resource_group.this.location - resource_group_name = azurerm_resource_group.this.name - sku = "Standard" + name = local.name + location = var.azure_location + resource_group_name = module.aks.resource_group + sku = "Basic" capacity = 1 + tags = var.tags } resource "azurerm_eventhub" "this" { - name = "eh-${local.name_suffix}" + name = local.name namespace_name = azurerm_eventhub_namespace.this.name - resource_group_name = azurerm_resource_group.this.name + resource_group_name = module.aks.resource_group partition_count = 1 message_retention = 1 } resource "azurerm_eventhub_authorization_rule" "this" { - name = "flux" - resource_group_name = azurerm_resource_group.this.name + name = local.name + resource_group_name = module.aks.resource_group namespace_name = azurerm_eventhub_namespace.this.name eventhub_name = azurerm_eventhub.this.name listen = true diff --git a/tests/integration/terraform/azure/keyvault.tf b/tests/integration/terraform/azure/keyvault.tf new file mode 100644 index 0000000000..b86adc8efb --- /dev/null +++ b/tests/integration/terraform/azure/keyvault.tf @@ -0,0 +1,61 @@ +resource "azurerm_key_vault" "this" { + name = local.name + resource_group_name = module.aks.resource_group + location = var.azure_location + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + tags = var.tags +} + +resource "azurerm_key_vault_access_policy" "admin" { + key_vault_id = azurerm_key_vault.this.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Create", + "Update", + "Encrypt", + "Delete", + "Get", + "List", + "Purge", + "Recover", + "GetRotationPolicy", + "SetRotationPolicy" + ] + + secret_permissions = [ + "Get", + "Delete", + "Purge", + "Recover" + ] + +} + +resource "azurerm_key_vault_access_policy" "cluster_binding" { + key_vault_id = azurerm_key_vault.this.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = module.aks.principal_id + + key_permissions = [ + "Decrypt", + "Encrypt", + ] +} + +resource "azurerm_key_vault_key" "sops" { + depends_on = [azurerm_key_vault_access_policy.admin] + + name = "sops" + key_vault_id = azurerm_key_vault.this.id + key_type = "RSA" + key_size = 2048 + tags = var.tags + + key_opts = [ + "decrypt", + "encrypt", + ] +} diff --git a/tests/integration/terraform/azure/main.tf b/tests/integration/terraform/azure/main.tf new file mode 100644 index 0000000000..e4d22b29df --- /dev/null +++ b/tests/integration/terraform/azure/main.tf @@ -0,0 +1,35 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">=3.20.0" + } + azuread = { + source = "hashicorp/azuread" + version = ">=2.28.0" + } + azuredevops = { + source = "microsoft/azuredevops" + version = ">=0.2.2" + } + } +} + +provider "azurerm" { + features {} +} + +provider "azuredevops" { + org_service_url = "https://dev.azure.com/${var.azuredevops_org}" + personal_access_token = var.azuredevops_pat +} + +data "azurerm_client_config" "current" {} + +resource "random_pet" "suffix" { + separator = "o" +} + +locals { + name = "e2e${random_pet.suffix.id}" +} diff --git a/tests/integration/terraform/azure/outputs.tf b/tests/integration/terraform/azure/outputs.tf new file mode 100644 index 0000000000..a7f089043f --- /dev/null +++ b/tests/integration/terraform/azure/outputs.tf @@ -0,0 +1,41 @@ +output "aks_kubeconfig" { + description = "kubeconfig of the created AKS cluster" + value = module.aks.kubeconfig + sensitive = true +} + +output "azure_devops_access_token" { + sensitive = true + value = var.azuredevops_pat +} + +output "fleet_infra_repository" { + value = { + http = azuredevops_git_repository.fleet_infra.remote_url + ssh = "ssh://git@ssh.dev.azure.com/v3/${var.azuredevops_org}/${azuredevops_git_repository.fleet_infra.project_id}/${azuredevops_git_repository.fleet_infra.name}" + } +} + +output "application_repository" { + value = { + http = azuredevops_git_repository.application.remote_url + ssh = "ssh://git@ssh.dev.azure.com/v3/${var.azuredevops_org}/${azuredevops_git_repository.application.project_id}/${azuredevops_git_repository.application.name}" + } +} + +output "aks_client_id" { + value = module.aks.kubelet_client_id +} + +output "event_hub_sas" { + value = azurerm_eventhub_authorization_rule.this.primary_connection_string + sensitive = true +} + +output "sops_id" { + value = azurerm_key_vault_key.sops.id +} + +output "acr_url" { + value = module.acr.registry_url +} diff --git a/tests/integration/terraform/azure/variables.tf b/tests/integration/terraform/azure/variables.tf new file mode 100644 index 0000000000..8f3b93760f --- /dev/null +++ b/tests/integration/terraform/azure/variables.tf @@ -0,0 +1,21 @@ +variable "azuredevops_org" { + type = string + description = "Name of Azure DevOps organizations were the repositories will be created" +} + +variable "azure_location" { + type = string + description = "Location of the resource group" + default = "eastus" +} + +variable "tags" { + type = map(string) + default = {} + description = "Tags for created Azure resources" +} + +variable "azuredevops_pat" { + type = string + description = "Personal access token for Azure DevOps repository" +} diff --git a/tests/integration/terraform/gcp/gke.tf b/tests/integration/terraform/gcp/gke.tf new file mode 100644 index 0000000000..5de455db73 --- /dev/null +++ b/tests/integration/terraform/gcp/gke.tf @@ -0,0 +1,13 @@ +module "gke" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/gke" + + name = local.name + tags = var.tags +} + +module "gcr" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/gcr" + + name = local.name + tags = var.tags +} diff --git a/tests/integration/terraform/gcp/kms.tf b/tests/integration/terraform/gcp/kms.tf new file mode 100644 index 0000000000..6aed72a305 --- /dev/null +++ b/tests/integration/terraform/gcp/kms.tf @@ -0,0 +1,18 @@ +data "google_kms_key_ring" "keyring" { + name = var.gcp_keyring + location = "global" +} + +data "google_kms_crypto_key" "my_crypto_key" { + name = var.gcp_crypto_key + key_ring = data.google_kms_key_ring.keyring.id +} + +resource "google_kms_key_ring_iam_binding" "key_ring" { + key_ring_id = data.google_kms_key_ring.keyring.id + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + + members = [ + "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com", + ] +} diff --git a/tests/integration/terraform/gcp/main.tf b/tests/integration/terraform/gcp/main.tf new file mode 100644 index 0000000000..cc917453db --- /dev/null +++ b/tests/integration/terraform/gcp/main.tf @@ -0,0 +1,13 @@ +provider "google" { + project = var.gcp_project_id + region = var.gcp_region + zone = var.gcp_zone +} + +resource "random_pet" "suffix" {} + +locals { + name = "e2e-${random_pet.suffix.id}" +} + +data "google_project" "project" {} diff --git a/tests/integration/terraform/gcp/outputs.tf b/tests/integration/terraform/gcp/outputs.tf new file mode 100644 index 0000000000..d356b1881e --- /dev/null +++ b/tests/integration/terraform/gcp/outputs.tf @@ -0,0 +1,32 @@ +output "gke_kubeconfig" { + value = module.gke.kubeconfig + sensitive = true +} + +output "gcp_project_id" { + value = var.gcp_project_id +} + +output "gcp_region" { + value = var.gcp_region +} + +output "artifact_registry_id" { + value = module.gcr.artifact_repository_id +} + +output "sops_id" { + value = data.google_kms_crypto_key.my_crypto_key.id +} + +output "fleet_infra_repository" { + value = "ssh://${var.gcp_email}@source.developers.google.com:2022/p/${var.gcp_project_id}/r/${google_sourcerepo_repository.fleet-infra.name}" +} + +output "application_repository" { + value = "ssh://${var.gcp_email}@source.developers.google.com:2022/p/${var.gcp_project_id}/r/${google_sourcerepo_repository.application.name}" +} + +output "pubsub_topic" { + value = google_pubsub_topic.pubsub.name +} diff --git a/tests/integration/terraform/gcp/pubsub.tf b/tests/integration/terraform/gcp/pubsub.tf new file mode 100644 index 0000000000..c59c0e99e7 --- /dev/null +++ b/tests/integration/terraform/gcp/pubsub.tf @@ -0,0 +1,11 @@ +resource "google_pubsub_topic" "pubsub" { + name = local.name + labels = var.tags + message_retention_duration = "7200s" +} + +resource "google_pubsub_subscription" "sub" { + project = var.gcp_project_id + name = local.name + topic = google_pubsub_topic.pubsub.name +} diff --git a/tests/integration/terraform/gcp/sourcerepo.tf b/tests/integration/terraform/gcp/sourcerepo.tf new file mode 100644 index 0000000000..4d7f5ee997 --- /dev/null +++ b/tests/integration/terraform/gcp/sourcerepo.tf @@ -0,0 +1,26 @@ +resource "google_sourcerepo_repository" "fleet-infra" { + name = "fleet-infra-${random_pet.suffix.id}" +} + +resource "google_sourcerepo_repository" "application" { + name = "application-${random_pet.suffix.id}" +} + +resource "google_sourcerepo_repository_iam_binding" "application_binding" { + project = google_sourcerepo_repository.application.project + repository = google_sourcerepo_repository.application.name + role = "roles/source.admin" + members = [ + "user:${var.gcp_email}", + ] +} + +resource "google_sourcerepo_repository_iam_binding" "fleet-infra_binding" { + project = google_sourcerepo_repository.fleet-infra.project + repository = google_sourcerepo_repository.fleet-infra.name + role = "roles/source.admin" + members = [ + "user:${var.gcp_email}", + ] +} + diff --git a/tests/integration/terraform/gcp/variables.tf b/tests/integration/terraform/gcp/variables.tf new file mode 100644 index 0000000000..6af9e7d61f --- /dev/null +++ b/tests/integration/terraform/gcp/variables.tf @@ -0,0 +1,37 @@ +variable "gcp_project_id" { + type = string + description = "GCP project to create the resources in" +} + +variable "gcp_email" { + type = string + description = "GCP user email" +} + +variable "gcp_region" { + type = string + default = "us-central1" + description = "GCP region" +} + +variable "gcp_zone" { + type = string + default = "us-central1" + description = "GCP zone" +} + +variable "gcp_keyring" { + type = string + description = "GCP keyring that contains crypto key for encrypting secrets" +} + +variable "gcp_crypto_key" { + type = string + description = "GCP crypto key for encrypting secrets" +} + +variable "tags" { + type = map(string) + default = {} + description = "tags for created resources" +} diff --git a/tests/integration/util_test.go b/tests/integration/util_test.go new file mode 100644 index 0000000000..9d1c463708 --- /dev/null +++ b/tests/integration/util_test.go @@ -0,0 +1,420 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "context" + "errors" + "fmt" + "io" + "net/url" + "os" + "strings" + "time" + + extgogit "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/google/go-containerregistry/pkg/crane" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + kerrors "k8s.io/apimachinery/pkg/util/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/git" + "github.com/fluxcd/pkg/git/gogit" + "github.com/fluxcd/pkg/git/repository" + "github.com/fluxcd/pkg/runtime/conditions" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + "github.com/fluxcd/test-infra/tftestenv" +) + +// installFlux adds the core Flux components to the cluster specified in the kubeconfig file. +func installFlux(ctx context.Context, tmpDir string, kubeconfigPath string) error { + // Create flux-system namespace + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + }, + } + err := testEnv.Create(ctx, &namespace) + if err != nil { + return err + } + + repoURL := getTransportURL(cfg.fleetInfraRepository) + if cfg.kustomizationYaml != "" { + files := make(map[string]io.Reader) + files["clusters/e2e/flux-system/kustomization.yaml"] = strings.NewReader(cfg.kustomizationYaml) + files["clusters/e2e/flux-system/gotk-components.yaml"] = strings.NewReader("") + files["clusters/e2e/flux-system/gotk-sync.yaml"] = strings.NewReader("") + c, err := getRepository(ctx, tmpDir, repoURL, defaultBranch, cfg.defaultAuthOpts) + if err != nil { + return err + } + + err = commitAndPushAll(ctx, c, files, defaultBranch) + if err != nil { + return err + } + } + + var bootstrapArgs string + if cfg.defaultGitTransport == git.SSH { + f, err := os.CreateTemp("", "flux-e2e-ssh-key-*") + if err != nil { + return err + } + err = os.WriteFile(f.Name(), []byte(cfg.gitPrivateKey), 0o600) + if err != nil { + return err + } + bootstrapArgs = fmt.Sprintf("--private-key-file=%s -s", f.Name()) + } else { + bootstrapArgs = fmt.Sprintf("--token-auth --password=%s", cfg.gitPat) + } + + bootstrapCmd := fmt.Sprintf("%s bootstrap git --url=%s %s --kubeconfig=%s --path=clusters/e2e "+ + " --components-extra image-reflector-controller,image-automation-controller", + fluxBin, repoURL, bootstrapArgs, kubeconfigPath) + + return tftestenv.RunCommand(ctx, "./", bootstrapCmd, tftestenv.RunCommandOptions{ + Timeout: 15 * time.Minute, + }) +} + +func runFluxCheck(ctx context.Context) error { + checkCmd := fmt.Sprintf("%s check --kubeconfig %s", fluxBin, kubeconfigPath) + return tftestenv.RunCommand(ctx, "./", checkCmd, tftestenv.RunCommandOptions{ + AttachConsole: true, + }) +} + +func uninstallFlux(ctx context.Context) error { + uninstallCmd := fmt.Sprintf("%s uninstall --kubeconfig %s -s", fluxBin, kubeconfigPath) + if err := tftestenv.RunCommand(ctx, "./", uninstallCmd, tftestenv.RunCommandOptions{ + Timeout: 15 * time.Minute, + }); err != nil { + return err + } + return nil +} + +// verifyGitAndKustomization checks that the gitrespository and kustomization combination are working properly. +func verifyGitAndKustomization(ctx context.Context, kubeClient client.Client, namespace, name string) error { + nn := types.NamespacedName{ + Name: name, + Namespace: namespace, + } + source := &sourcev1.GitRepository{} + if err := kubeClient.Get(ctx, nn, source); err != nil { + return err + } + if err := checkReadyCondition(source); err != nil { + return err + } + + kustomization := &kustomizev1.Kustomization{} + if err := kubeClient.Get(ctx, nn, kustomization); err != nil { + return err + } + if err := checkReadyCondition(kustomization); err != nil { + return err + } + + return nil +} + +type nsConfig struct { + repoURL string + ref *sourcev1.GitRepositoryRef + protocol git.TransportType + objectName string + path string + modifyKsSpec func(spec *kustomizev1.KustomizationSpec) +} + +// setUpFluxConfigs creates the namespace, then creates the git secret, +// git repository and kustomization in that namespace +func setUpFluxConfig(ctx context.Context, name string, opts nsConfig) error { + transport := cfg.defaultGitTransport + if opts.protocol != "" { + transport = opts.protocol + } + + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + if err := testEnv.Create(ctx, &namespace); err != nil && !apierrors.IsAlreadyExists(err) { + return err + } + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "git-credentials", + Namespace: name, + }, + } + + secret.StringData = map[string]string{ + "username": cfg.gitUsername, + "password": cfg.gitPat, + } + + if transport == git.SSH { + secret.StringData = map[string]string{ + "identity": cfg.gitPrivateKey, + "identity.pub": cfg.gitPublicKey, + "known_hosts": cfg.knownHosts, + } + } + if err := testEnv.Create(ctx, &secret); err != nil { + return err + } + + ref := &sourcev1.GitRepositoryRef{ + Branch: name, + } + + if opts.ref != nil { + ref = opts.ref + } + + gitSpec := &sourcev1.GitRepositorySpec{ + Interval: metav1.Duration{ + Duration: 1 * time.Minute, + }, + Reference: ref, + SecretRef: &meta.LocalObjectReference{ + Name: secret.Name, + }, + URL: opts.repoURL, + } + + source := &sourcev1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}, + Spec: *gitSpec, + } + if err := testEnv.Create(ctx, source); err != nil { + return err + } + + ksSpec := &kustomizev1.KustomizationSpec{ + Path: opts.path, + TargetNamespace: name, + SourceRef: kustomizev1.CrossNamespaceSourceReference{ + Kind: sourcev1.GitRepositoryKind, + Name: source.Name, + Namespace: source.Namespace, + }, + Interval: metav1.Duration{ + Duration: 1 * time.Minute, + }, + Prune: true, + } + if opts.modifyKsSpec != nil { + opts.modifyKsSpec(ksSpec) + } + kustomization := &kustomizev1.Kustomization{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}, + Spec: *ksSpec, + } + + return testEnv.Create(ctx, kustomization) +} + +func tearDownFluxConfig(ctx context.Context, name string) error { + var allErr []error + + source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}} + if err := testEnv.Delete(ctx, source); err != nil { + allErr = append(allErr, err) + } + + kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}} + if err := testEnv.Delete(ctx, kustomization); err != nil { + allErr = append(allErr, err) + } + + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + if err := testEnv.Delete(ctx, &namespace); err != nil { + allErr = append(allErr, err) + } + + return kerrors.NewAggregate(allErr) +} + +// getRepository and clones the git repository to the directory. +func getRepository(ctx context.Context, dir, repoURL, branchName string, authOpts *git.AuthOptions) (*gogit.Client, error) { + c, err := gogit.NewClient(dir, authOpts, gogit.WithSingleBranch(false), gogit.WithDiskStorage()) + if err != nil { + return nil, err + } + + _, err = c.Clone(ctx, repoURL, repository.CloneConfig{ + CheckoutStrategy: repository.CheckoutStrategy{ + Branch: branchName, + }, + }) + if err != nil { + return nil, err + } + + return c, nil +} + +// commitAndPushAll checks out to the specified branch, creates the files, commits and then pushes them to +// the remote git repository. +func commitAndPushAll(ctx context.Context, client *gogit.Client, files map[string]io.Reader, branchName string) error { + err := client.SwitchBranch(ctx, branchName) + if err != nil && !errors.Is(err, plumbing.ErrReferenceNotFound) { + return err + } + + _, err = client.Commit(git.Commit{ + Author: git.Signature{ + Name: git.DefaultPublicKeyAuthUser, + Email: "test@example.com", + When: time.Now(), + }, + }, repository.WithFiles(files)) + if err != nil { + if errors.Is(err, git.ErrNoStagedFiles) { + return nil + } + + return err + } + + err = client.Push(ctx, repository.PushConfig{}) + if err != nil { + return fmt.Errorf("unable to push: %s", err) + } + + return nil +} + +func createTagAndPush(ctx context.Context, client *gogit.Client, branchName, newTag string) error { + repo, err := extgogit.PlainOpen(client.Path()) + if err != nil { + return err + } + + ref, err := repo.Reference(plumbing.NewBranchReferenceName(branchName), false) + if err != nil { + return err + } + + tags, err := repo.TagObjects() + if err != nil { + return err + } + + err = tags.ForEach(func(tag *object.Tag) error { + if tag.Name == newTag { + err = repo.DeleteTag(tag.Name) + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + return fmt.Errorf("error deleting local tag: %w", err) + } + + // Delete remote tag + if err := client.Push(ctx, repository.PushConfig{ + Refspecs: []string{fmt.Sprintf(":refs/tags/%s", newTag)}, + Force: true, + }); err != nil && !errors.Is(err, extgogit.NoErrAlreadyUpToDate) { + return fmt.Errorf("unable to delete existing tag: %w", err) + } + + sig := &object.Signature{ + Name: git.DefaultPublicKeyAuthUser, + Email: "test@example.com", + When: time.Now(), + } + if _, err = repo.CreateTag(newTag, ref.Hash(), &extgogit.CreateTagOptions{ + Tagger: sig, + Message: "create tag", + }); err != nil { + return fmt.Errorf("unable to create tag: %w", err) + } + + return client.Push(ctx, repository.PushConfig{ + Refspecs: []string{"refs/tags/*:refs/tags/*"}, + }) +} + +func pushImagesFromURL(repoURL, imgURL string, tags []string) error { + img, err := crane.Pull(imgURL) + if err != nil { + return err + } + + for _, tag := range tags { + if err := crane.Push(img, fmt.Sprintf("%s:%s", repoURL, tag)); err != nil { + return err + } + } + + return nil +} + +func getTransportURL(urls gitUrl) string { + if cfg.defaultGitTransport == git.SSH { + return urls.ssh + } + + return urls.http +} + +func authOpts(repoURL string, authData map[string][]byte) (*git.AuthOptions, error) { + u, err := url.Parse(repoURL) + if err != nil { + return nil, err + } + + return git.NewAuthOptions(*u, authData) +} + +// checkReadyCondition checks for a Ready condition, it returns nil if the condition is true +// or an error (with the message if the Ready condition is present). +func checkReadyCondition(from conditions.Getter) error { + if conditions.IsReady(from) { + return nil + } + errMsg := fmt.Sprintf("object not ready") + readyMsg := conditions.GetMessage(from, meta.ReadyCondition) + if readyMsg != "" { + errMsg += ": " + readyMsg + } + return errors.New(errMsg) +}