Compare commits
No commits in common. "main" and "v0.14.1" have entirely different histories.
|
@ -1 +0,0 @@
|
|||
build/
|
|
@ -0,0 +1,19 @@
|
|||
FROM golang:1.16-buster as builder
|
||||
|
||||
# Up-to-date libgit2 dependencies are only available in sid (unstable).
|
||||
RUN echo "deb http://deb.debian.org/debian unstable main" >> /etc/apt/sources.list \
|
||||
&& echo "deb-src http://deb.debian.org/debian unstable main" >> /etc/apt/sources.list
|
||||
RUN set -eux; \
|
||||
apt-get update \
|
||||
&& apt-get install -y libgit2-dev/unstable zlib1g-dev/unstable libssh2-1-dev/unstable libpcre3-dev/unstable \
|
||||
&& apt-get clean \
|
||||
&& apt-get autoremove --purge -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN groupadd -g 116 test && \
|
||||
useradd -u 1001 --gid test --shell /bin/sh --create-home test
|
||||
|
||||
# Run as test user
|
||||
USER test
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "-c"]
|
|
@ -0,0 +1,12 @@
|
|||
name: 'Run tests'
|
||||
description: 'Run tests in docker container'
|
||||
inputs:
|
||||
command:
|
||||
description: 'Command to run inside the container'
|
||||
required: true
|
||||
default: 'make test'
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
args:
|
||||
- ${{ inputs.command }}
|
|
@ -1,32 +0,0 @@
|
|||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
labels: ["dependencies"]
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
groups:
|
||||
go-deps:
|
||||
patterns:
|
||||
- "*"
|
||||
allow:
|
||||
- dependency-type: "direct"
|
||||
ignore:
|
||||
# Kubernetes deps are updated by fluxcd/pkg
|
||||
- dependency-name: "k8s.io/*"
|
||||
- dependency-name: "sigs.k8s.io/*"
|
||||
- dependency-name: "github.com/go-logr/*"
|
||||
# Flux APIs are updated at release time
|
||||
- dependency-name: "github.com/fluxcd/image-automation-controller/api"
|
||||
- dependency-name: "github.com/fluxcd/image-reflector-controller/api"
|
||||
- dependency-name: "github.com/fluxcd/source-controller/api"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
labels: ["area/ci", "dependencies"]
|
||||
groups:
|
||||
ci:
|
||||
patterns:
|
||||
- "*"
|
||||
schedule:
|
||||
interval: "monthly"
|
|
@ -1,14 +0,0 @@
|
|||
# Configuration file to declaratively configure labels
|
||||
# Ref: https://github.com/EndBug/label-sync#Config-files
|
||||
|
||||
- name: area/git
|
||||
description: Git related issues and pull requests
|
||||
color: '#863faf'
|
||||
- name: area/kyaml
|
||||
description: YAML patching related issues and pull requests
|
||||
color: '#86dbf2'
|
||||
|
||||
# TODO: enable this when we have a release/v1.0.x branch
|
||||
#- name: backport:release/v1.0.x
|
||||
# description: To be backported to release/v1.0.x
|
||||
# color: '#ffd700'
|
|
@ -1,31 +0,0 @@
|
|||
name: backport
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed, labeled]
|
||||
|
||||
jobs:
|
||||
pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Create backport PRs
|
||||
uses: korthout/backport-action@ca4972adce8039ff995e618f5fc02d1b7961f27a # v3.3.0
|
||||
# xref: https://github.com/korthout/backport-action#inputs
|
||||
with:
|
||||
# Use token to allow workflows to be triggered for the created PR
|
||||
github_token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
# Match labels with a pattern `backport:<target-branch>`
|
||||
label_pattern: '^backport:([^ ]+)$'
|
||||
# A bit shorter pull-request title than the default
|
||||
pull_title: '[${target_branch}] ${pull_title}'
|
||||
# Simpler PR description than default
|
||||
pull_description: |-
|
||||
Automated backport to `${target_branch}`, triggered by a label in #${pull_number}.
|
|
@ -1,42 +1,29 @@
|
|||
name: build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ 'main', 'release/**' ]
|
||||
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
test-linux-amd64:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
uses: actions/checkout@v2
|
||||
- name: Restore go cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
- name: Setup Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
# the ff is mounted into the container as ~/go/pkg/mod
|
||||
path: /home/runner/work/_temp/_github_home/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Set up kubebuilder
|
||||
uses: fluxcd/pkg/actions/kubebuilder@main
|
||||
- name: Run tests
|
||||
run: make test
|
||||
- name: Verify
|
||||
run: make verify
|
||||
- name: Build multi-arch container image
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
push: false
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
tags: |
|
||||
${{ github.repository }}:latest
|
||||
uses: ./.github/actions/run-tests
|
||||
env:
|
||||
GOPATH: /github/home/go
|
||||
KUBEBUILDER_ASSETS: ${{ github.workspace }}/kubebuilder/bin
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
name: fuzz
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ 'main', 'release/**' ]
|
||||
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
|
||||
jobs:
|
||||
smoketest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Smoke test Fuzzers
|
||||
run: make fuzz-smoketest
|
|
@ -0,0 +1,33 @@
|
|||
name: nightly
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: all
|
||||
- name: Setup Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
buildkitd-flags: "--debug"
|
||||
- name: Build multi-arch container image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: false
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
tags: |
|
||||
${{ env.REPOSITORY }}:nightly
|
|
@ -7,29 +7,17 @@ on:
|
|||
inputs:
|
||||
tag:
|
||||
description: 'image tag prefix'
|
||||
default: 'preview'
|
||||
default: 'rc'
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CONTROLLER: ${{ github.event.repository.name }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
outputs:
|
||||
hashes: ${{ steps.slsa.outputs.hashes }}
|
||||
image_url: ${{ steps.slsa.outputs.image_url }}
|
||||
image_digest: ${{ steps.slsa.outputs.image_digest }}
|
||||
build-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # for creating the GitHub release.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for pushing and signing container images.
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@main
|
||||
- name: Prepare
|
||||
|
@ -39,122 +27,65 @@ jobs:
|
|||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
VERSION=${GITHUB_REF/refs\/tags\//}
|
||||
fi
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo ::set-output name=BUILD_DATE::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
echo ::set-output name=VERSION::${VERSION}
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: all
|
||||
- name: Setup Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
buildkitd-flags: "--debug"
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||
- name: Generate images meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
- name: Publish multi-arch container image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
images: |
|
||||
fluxcd/${{ env.CONTROLLER }}
|
||||
ghcr.io/fluxcd/${{ env.CONTROLLER }}
|
||||
tags: |
|
||||
type=raw,value=${{ steps.prep.outputs.VERSION }}
|
||||
- name: Publish images
|
||||
id: build-push
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
sbom: true
|
||||
provenance: true
|
||||
push: true
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
|
||||
- name: Sign images
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: 1
|
||||
tags: |
|
||||
ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
docker.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=${{ github.event.repository.name }}
|
||||
org.opencontainers.image.description=${{ github.event.repository.description }}
|
||||
org.opencontainers.image.url=${{ github.event.repository.html_url }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.version=${{ steps.prep.outputs.VERSION }}
|
||||
org.opencontainers.image.created=${{ steps.prep.outputs.BUILD_DATE }}
|
||||
- name: Check images
|
||||
run: |
|
||||
cosign sign --yes fluxcd/${{ env.CONTROLLER }}@${{ steps.build-push.outputs.digest }}
|
||||
cosign sign --yes ghcr.io/fluxcd/${{ env.CONTROLLER }}@${{ steps.build-push.outputs.digest }}
|
||||
- name: Generate release artifacts
|
||||
docker buildx imagetools inspect docker.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
docker buildx imagetools inspect ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
docker pull docker.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
docker pull ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
- name: Generate release manifests
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
mkdir -p config/release
|
||||
kustomize build ./config/crd > ./config/release/${{ env.CONTROLLER }}.crds.yaml
|
||||
kustomize build ./config/manager > ./config/release/${{ env.CONTROLLER }}.deployment.yaml
|
||||
- uses: anchore/sbom-action/download-syft@da167eac915b4e86f08b264dbdbc867b61be6f0c # v0.20.5
|
||||
- name: Create release and SBOM
|
||||
id: run-goreleaser
|
||||
- name: Create release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean --skip=validate
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Generate SLSA metadata
|
||||
id: slsa
|
||||
env:
|
||||
ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
|
||||
run: |
|
||||
hashes=$(echo -E $ARTIFACTS | jq --raw-output '.[] | {name, "digest": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join(" ") | sub("^sha256:";"")' | base64 -w0)
|
||||
echo "hashes=$hashes" >> $GITHUB_OUTPUT
|
||||
|
||||
image_url=fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.version }}
|
||||
echo "image_url=$image_url" >> $GITHUB_OUTPUT
|
||||
|
||||
image_digest=${{ steps.build-push.outputs.digest }}
|
||||
echo "image_digest=$image_digest" >> $GITHUB_OUTPUT
|
||||
|
||||
release-provenance:
|
||||
needs: [release]
|
||||
permissions:
|
||||
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.
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
with:
|
||||
provenance-name: "provenance.intoto.jsonl"
|
||||
base64-subjects: "${{ needs.release.outputs.hashes }}"
|
||||
upload-assets: true
|
||||
|
||||
dockerhub-provenance:
|
||||
needs: [release]
|
||||
permissions:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for uploading attestations.
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: ${{ needs.release.outputs.image_url }}
|
||||
digest: ${{ needs.release.outputs.image_digest }}
|
||||
registry-username: fluxcdbot
|
||||
secrets:
|
||||
registry-password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||
|
||||
ghcr-provenance:
|
||||
needs: [release]
|
||||
permissions:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for uploading attestations.
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: ghcr.io/${{ needs.release.outputs.image_url }}
|
||||
digest: ${{ needs.release.outputs.image_digest }}
|
||||
registry-username: fluxcdbot
|
||||
secrets:
|
||||
registry-password: ${{ secrets.GHCR_TOKEN }}
|
||||
prerelease: true
|
||||
artifacts: "config/release/*.yaml"
|
||||
artifactContentType: "text/plain"
|
||||
body: |
|
||||
[CHANGELOG](https://github.com/fluxcd/${{ env.CONTROLLER }}/blob/main/CHANGELOG.md)
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
@ -1,26 +1,21 @@
|
|||
name: scan
|
||||
name: Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ 'main', 'release/**' ]
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ 'main', 'release/**' ]
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '18 10 * * 3'
|
||||
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
security-events: write # for codeQL to write security events
|
||||
|
||||
jobs:
|
||||
fossa:
|
||||
name: FOSSA
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run FOSSA scan and upload build data
|
||||
uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1
|
||||
uses: fossa-contrib/fossa-action@v1
|
||||
with:
|
||||
# FOSSA Push-Only API Token
|
||||
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
||||
|
@ -30,23 +25,13 @@ jobs:
|
|||
name: CodeQL
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11
|
||||
uses: github/codeql-action/init@v1
|
||||
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@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
name: sync-labels
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- .github/labels.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
name: Run sync
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3
|
||||
with:
|
||||
# Configuration file
|
||||
config-file: |
|
||||
https://raw.githubusercontent.com/fluxcd/community/main/.github/standard-labels.yaml
|
||||
.github/labels.yaml
|
||||
# Strictly declarative
|
||||
delete-other-labels: true
|
|
@ -2,7 +2,7 @@ notes
|
|||
|
||||
# Thes are downloaded in the Makefile
|
||||
cache/*
|
||||
internal/controller/testdata/crds/*
|
||||
controllers/testdata/crds/*
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
|
@ -11,7 +11,6 @@ internal/controller/testdata/crds/*
|
|||
*.so
|
||||
*.dylib
|
||||
bin
|
||||
testbin
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
@ -28,9 +27,3 @@ testbin
|
|||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Exclude all build related files
|
||||
build/
|
||||
|
||||
# CRDs for fuzzing tests.
|
||||
internal/controller/testdata/crd
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
project_name: image-automation-controller
|
||||
|
||||
builds:
|
||||
- skip: true
|
||||
|
||||
release:
|
||||
extra_files:
|
||||
- glob: config/release/*.yaml
|
||||
prerelease: "true"
|
||||
header: |
|
||||
## Changelog
|
||||
|
||||
[{{.Tag}} changelog](https://github.com/fluxcd/{{.ProjectName}}/blob/{{.Tag}}/CHANGELOG.md)
|
||||
footer: |
|
||||
## Container images
|
||||
|
||||
- `docker.io/fluxcd/{{.ProjectName}}:{{.Tag}}`
|
||||
- `ghcr.io/fluxcd/{{.ProjectName}}:{{.Tag}}`
|
||||
|
||||
Supported architectures: `linux/amd64`, `linux/arm64` and `linux/arm/v7`.
|
||||
|
||||
The container images are built on GitHub hosted runners and are signed with cosign and GitHub OIDC.
|
||||
To verify the images and their provenance (SLSA level 3), please see the [security documentation](https://fluxcd.io/flux/security/).
|
||||
|
||||
changelog:
|
||||
disable: true
|
||||
|
||||
checksum:
|
||||
extra_files:
|
||||
- glob: config/release/*.yaml
|
||||
|
||||
source:
|
||||
enabled: true
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_source_code"
|
||||
|
||||
sboms:
|
||||
- id: source
|
||||
artifacts: source
|
||||
documents:
|
||||
- "{{ .ProjectName }}_{{ .Version }}_sbom.spdx.json"
|
||||
|
||||
# signs the checksum file
|
||||
# all files (including the sboms) are included in the checksum
|
||||
# https://goreleaser.com/customization/sign
|
||||
signs:
|
||||
- cmd: cosign
|
||||
env:
|
||||
- COSIGN_EXPERIMENTAL=1
|
||||
certificate: "${artifact}.pem"
|
||||
args:
|
||||
- sign-blob
|
||||
- "--yes"
|
||||
- "--output-certificate=${certificate}"
|
||||
- "--output-signature=${signature}"
|
||||
- "${artifact}"
|
||||
artifacts: checksum
|
||||
output: true
|
1328
CHANGELOG.md
1328
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,72 @@
|
|||
# Contributing
|
||||
|
||||
Image automation controller is [Apache 2.0 licensed](LICENSE) and accepts contributions
|
||||
via GitHub pull requests. This document outlines some of the conventions on
|
||||
to make it easier to get your contribution accepted.
|
||||
|
||||
We gratefully welcome improvements to issues and documentation as well as to
|
||||
code.
|
||||
|
||||
## Certificate of Origin
|
||||
|
||||
By contributing to this project you agree to the Developer Certificate of
|
||||
Origin (DCO). This document was created by the Linux Kernel community and is a
|
||||
simple statement that you, as a contributor, have the legal right to make the
|
||||
contribution. No action from you is required, but it's a good idea to see the
|
||||
[DCO](DCO) file for details before you start contributing code.
|
||||
|
||||
## Communications
|
||||
|
||||
The project uses Slack: To join the conversation, simply join the
|
||||
[CNCF](https://slack.cncf.io/) Slack workspace and use the
|
||||
[#flux](https://cloud-native.slack.com/messages/flux/) channel.
|
||||
|
||||
The developers use a mailing list to discuss development as well.
|
||||
Simply subscribe to [flux-dev on cncf.io](https://lists.cncf.io/g/cncf-flux-dev)
|
||||
to join the conversation (this will also add an invitation to your
|
||||
Google calendar for our [Flux
|
||||
meeting](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/edit#)).
|
||||
|
||||
### How to run the test suite
|
||||
|
||||
Prerequisites:
|
||||
* go >= 1.16
|
||||
* kubebuilder >= 2.3
|
||||
* kustomize >= 3.1
|
||||
|
||||
You can run the unit tests by simply doing
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
## Acceptance policy
|
||||
|
||||
These things will make a PR more likely to be accepted:
|
||||
|
||||
- a well-described requirement
|
||||
- tests for new code
|
||||
- tests for old code!
|
||||
- new code and tests follow the conventions in old code and tests
|
||||
- a good commit message (see below)
|
||||
- all code must abide [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
|
||||
- names should abide [What's in a name](https://talks.golang.org/2014/names.slide#1)
|
||||
- code must build on both Linux and Darwin, via plain `go build`
|
||||
- code should have appropriate test coverage and tests should be written
|
||||
to work with `go test`
|
||||
|
||||
In general, we will merge a PR once one maintainer has endorsed it.
|
||||
For substantial changes, more people may become involved, and you might
|
||||
get asked to resubmit the PR or divide the changes into more than one PR.
|
||||
|
||||
### Format of the Commit Message
|
||||
|
||||
For this project we prefer the following rules for good commit messages:
|
||||
|
||||
- Limit the subject to 50 characters and write as the continuation
|
||||
of the sentence "If applied, this commit will ..."
|
||||
- Explain what and why in the body, if more than a trivial change;
|
||||
wrap it at 72 characters.
|
||||
|
||||
The [following article](https://chris.beams.io/posts/git-commit/#seven-rules)
|
||||
has some more helpful advice on documenting your work.
|
106
DEVELOPMENT.md
106
DEVELOPMENT.md
|
@ -1,106 +0,0 @@
|
|||
# Development
|
||||
|
||||
> **Note:** Please take a look at <https://fluxcd.io/contributing/flux/>
|
||||
> to find out about how to contribute to Flux and how to interact with the
|
||||
> Flux Development team.
|
||||
|
||||
## Installing required dependencies
|
||||
|
||||
There are a number of dependencies required to be able to run the controller and its test suite locally:
|
||||
|
||||
- [Install Go](https://golang.org/doc/install)
|
||||
- [Install Kustomize](https://kubernetes-sigs.github.io/kustomize/installation/)
|
||||
- [Install Docker](https://docs.docker.com/engine/install/)
|
||||
- (Optional) [Install Kubebuilder](https://book.kubebuilder.io/quick-start.html#installation)
|
||||
|
||||
The following dependencies are also used by some of the `make` targets:
|
||||
|
||||
- `controller-gen` (v0.19.0)
|
||||
- `gen-crd-api-reference-docs` (v0.3.0)
|
||||
- `setup-envtest` (latest)
|
||||
|
||||
If any of the above dependencies are not present on your system, the first invocation of a `make` target that requires them will install them.
|
||||
|
||||
## How to run the test suite
|
||||
|
||||
Prerequisites:
|
||||
* Go >= 1.25
|
||||
|
||||
You can run the test suite by simply doing
|
||||
|
||||
```sh
|
||||
make test
|
||||
```
|
||||
## How to run the controller locally
|
||||
|
||||
Install the controller's CRDs on your test cluster:
|
||||
|
||||
```sh
|
||||
make install
|
||||
```
|
||||
|
||||
Note that `image-automation-controller` depends on [source-controller](https://github.com/fluxcd/source-controller) to acquire its artifacts and [image-reflector-controller](https://github.com/fluxcd/image-reflector-controller) to access container image metadata. Ensure that they are both running on your test cluster prior to running the `image-automation-controller`.
|
||||
|
||||
Run the controller locally:
|
||||
|
||||
```sh
|
||||
make run
|
||||
```
|
||||
|
||||
## How to install the controller
|
||||
|
||||
### Building the container image
|
||||
|
||||
Set the name of the container image to be created from the source code. This will be used when building, pushing and referring to the image on YAML files:
|
||||
|
||||
```sh
|
||||
export IMG=registry-path/kustomize-controller
|
||||
export TAG=latest
|
||||
```
|
||||
Build and push the container image, tagging it as `$(IMG):$(TAG)`:
|
||||
|
||||
```sh
|
||||
BUILD_ARGS=--push make docker-build
|
||||
```
|
||||
**Note**: `make docker-build` will build images for the `amd64`,`arm64` and `arm/v7` architectures.
|
||||
|
||||
If you get the following error when building the docker container:
|
||||
```
|
||||
Multiple platforms feature is currently not supported for docker driver.
|
||||
Please switch to a different driver (eg. "docker buildx create --use")
|
||||
```
|
||||
|
||||
you may need to create and switch to a new builder that supports multiple platforms:
|
||||
|
||||
```sh
|
||||
docker buildx create --use
|
||||
```
|
||||
|
||||
### Deploying into a cluster
|
||||
|
||||
Deploy `image-automation-controller` into the cluster that is configured in the local kubeconfig file (i.e. `~/.kube/config`):
|
||||
|
||||
```sh
|
||||
make deploy
|
||||
```
|
||||
|
||||
### Debugging controller with VSCode
|
||||
|
||||
Create a `.vscode/launch.json` file:
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/main.go"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Start debugging by either clicking `Run` > `Start Debugging` or using
|
||||
the relevant shortcut.
|
70
Dockerfile
70
Dockerfile
|
@ -1,47 +1,59 @@
|
|||
ARG GO_VERSION=1.25
|
||||
ARG XX_VERSION=1.6.1
|
||||
FROM golang:1.16-buster as builder
|
||||
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
# Up-to-date libgit2 dependencies are only available in sid (unstable).
|
||||
# The libgit2 dependencies must be listed here to be able to build on ARM64.
|
||||
RUN echo "deb http://deb.debian.org/debian unstable main" >> /etc/apt/sources.list \
|
||||
&& echo "deb-src http://deb.debian.org/debian unstable main" >> /etc/apt/sources.list
|
||||
RUN set -eux; \
|
||||
apt-get update \
|
||||
&& apt-get install -y libgit2-dev/unstable zlib1g-dev/unstable libssh2-1-dev/unstable libpcre3-dev/unstable \
|
||||
&& apt-get clean \
|
||||
&& apt-get autoremove --purge -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Docker buildkit multi-arch build requires golang alpine
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS builder
|
||||
|
||||
# Copy the build utilities.
|
||||
COPY --from=xx / /
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Configure workspace
|
||||
WORKDIR /workspace
|
||||
|
||||
# Copy api submodule
|
||||
COPY api/ api/
|
||||
|
||||
# Copy modules manifests
|
||||
# Copy the Go Modules manifests
|
||||
COPY go.mod go.mod
|
||||
COPY go.sum go.sum
|
||||
|
||||
# Cache modules
|
||||
# This has its own go.mod, which needs to be present so go mod
|
||||
# download works.
|
||||
COPY api/ api/
|
||||
|
||||
# cache deps before building and copying source so that we don't need to re-download as much
|
||||
# and so that source changes don't invalidate our downloaded layer
|
||||
RUN go mod download
|
||||
|
||||
# Copy source code
|
||||
# Copy the go source
|
||||
COPY main.go main.go
|
||||
COPY internal/ internal/
|
||||
COPY pkg/ pkg/
|
||||
COPY controllers/ controllers/
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
ARG TARGETARCH
|
||||
# Build
|
||||
RUN CGO_ENABLED=1 go build -o image-automation-controller main.go
|
||||
|
||||
# build without specifing the arch
|
||||
ENV CGO_ENABLED=0
|
||||
RUN xx-go build -trimpath -a -o image-automation-controller main.go
|
||||
FROM debian:buster-slim as controller
|
||||
|
||||
FROM alpine:3.22
|
||||
LABEL org.opencontainers.image.source="https://github.com/fluxcd/image-automation-controller"
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN apk --no-cache add ca-certificates \
|
||||
&& update-ca-certificates
|
||||
# Up-to-date libgit2 dependencies are only available in
|
||||
# unstable, as libssh2 in testing/bullseye has been linked
|
||||
# against gcrypt which causes issues with PKCS* formats.
|
||||
RUN echo "deb http://deb.debian.org/debian unstable main" >> /etc/apt/sources.list \
|
||||
&& echo "deb-src http://deb.debian.org/debian unstable main" >> /etc/apt/sources.list
|
||||
RUN set -eux; \
|
||||
apt-get update \
|
||||
&& apt-get install -y ca-certificates libgit2-1.1 \
|
||||
&& apt-get clean \
|
||||
&& apt-get autoremove --purge -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /workspace/image-automation-controller /usr/local/bin/
|
||||
|
||||
USER 65534:65534
|
||||
RUN groupadd controller && \
|
||||
useradd --gid controller --shell /bin/sh --create-home controller
|
||||
|
||||
USER controller
|
||||
|
||||
ENTRYPOINT [ "image-automation-controller" ]
|
||||
|
|
|
@ -9,9 +9,4 @@ from the main Flux v2 git repository, as listed in
|
|||
|
||||
In alphabetical order:
|
||||
|
||||
Dipti Pai, Microsoft <diptipai@microsoft.com> (github: @dipti-pai, slack: Dipti Pai)
|
||||
Paulo Gomes, SUSE <pjbgf@linux.com> (github: @pjbgf, slack: pjbgf)
|
||||
|
||||
Retired maintainers:
|
||||
|
||||
- Michael Bridgen
|
||||
Michael Bridgen, Weaveworks <michael@weave.works> (github: @squaremo, slack: Michael Bridgen)
|
||||
|
|
237
Makefile
237
Makefile
|
@ -1,70 +1,30 @@
|
|||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= fluxcd/image-automation-controller
|
||||
# Image tag to use all building/push image targets
|
||||
TAG ?= latest
|
||||
|
||||
IMG ?= fluxcd/image-automation-controller:latest
|
||||
# Produce CRDs that work back to Kubernetes 1.16
|
||||
CRD_OPTIONS ?= crd:crdVersions=v1
|
||||
|
||||
# Allows for defining additional Docker buildx arguments,
|
||||
# e.g. '--push'.
|
||||
BUILD_ARGS ?=
|
||||
# Architectures to build images for
|
||||
BUILD_PLATFORMS ?= linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
# Allows for defining additional Go test args, e.g. '-tags integration'.
|
||||
GO_TEST_ARGS ?= -race
|
||||
|
||||
# Directory with versioned, downloaded things
|
||||
CACHE := cache
|
||||
CACHE:=cache
|
||||
|
||||
# Version of the source-controller from which to get the GitRepository CRD.
|
||||
# Pulls source-controller/api's version set in go.mod.
|
||||
SOURCE_VER ?= $(shell go list -m github.com/fluxcd/source-controller/api | awk '{print $$2}')
|
||||
# Change this if you bump the source-controller/api version in go.mod.
|
||||
SOURCE_VER ?= v0.15.4
|
||||
|
||||
# Version of the image-reflector-controller from which to get the ImagePolicy CRD.
|
||||
# Pulls image-reflector-controller/api's version set in go.mod.
|
||||
REFLECTOR_VER ?= $(shell go list -m github.com/fluxcd/image-reflector-controller/api | awk '{print $$2}')
|
||||
# Change this if you bump the image-reflector-controller/api version in go.mod.
|
||||
REFLECTOR_VER ?= v0.11.1
|
||||
|
||||
# Repository root based on Git metadata.
|
||||
REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel)
|
||||
BUILD_DIR := $(REPOSITORY_ROOT)/build
|
||||
|
||||
# FUZZ_TIME defines the max amount of time, in Go Duration,
|
||||
# each fuzzer should run for.
|
||||
FUZZ_TIME ?= 1m
|
||||
|
||||
# API (doc) generation utilities
|
||||
CONTROLLER_GEN_VERSION ?= v0.19.0
|
||||
GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113
|
||||
|
||||
# If gobin not set, create one on ./build and add to path.
|
||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
export GOBIN=$(BUILD_DIR)/gobin
|
||||
GOBIN=$(shell go env GOPATH)/bin
|
||||
else
|
||||
export GOBIN=$(shell go env GOBIN)
|
||||
endif
|
||||
export PATH:=${GOBIN}:${PATH}
|
||||
|
||||
# Architecture to use envtest with
|
||||
ifeq ($(shell uname -m),x86_64)
|
||||
ENVTEST_ARCH ?= amd64
|
||||
else
|
||||
ENVTEST_ARCH ?= arm64
|
||||
GOBIN=$(shell go env GOBIN)
|
||||
endif
|
||||
|
||||
ifeq ($(shell uname -s),Darwin)
|
||||
# Envtest only supports darwin-amd64
|
||||
ENVTEST_ARCH=amd64
|
||||
endif
|
||||
|
||||
TEST_CRDS := internal/controller/testdata/crds
|
||||
TEST_CRDS:=controllers/testdata/crds
|
||||
|
||||
# Log level for `make run`
|
||||
LOG_LEVEL ?= info
|
||||
|
||||
# Architecture to use envtest with
|
||||
ENVTEST_ARCH ?= amd64
|
||||
LOG_LEVEL?=info
|
||||
|
||||
all: manager
|
||||
|
||||
|
@ -92,143 +52,106 @@ ${CACHE}/imagepolicies_${REFLECTOR_VER}.yaml:
|
|||
curl -s --fail https://raw.githubusercontent.com/fluxcd/image-reflector-controller/${REFLECTOR_VER}/config/crd/bases/image.toolkit.fluxcd.io_imagepolicies.yaml \
|
||||
-o ${CACHE}/imagepolicies_${REFLECTOR_VER}.yaml
|
||||
|
||||
KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)"
|
||||
test: tidy test-api test_deps generate fmt vet manifests api-docs install-envtest ## Run tests
|
||||
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) \
|
||||
go test $(GO_TEST_ARGS) ./... -coverprofile cover.out
|
||||
# Run tests
|
||||
test: test_deps generate fmt vet manifests api-docs
|
||||
go test ./... -coverprofile cover.out
|
||||
cd api; go test ./... -coverprofile cover.out
|
||||
|
||||
test-api: ## Run api tests
|
||||
cd api; go test $(GO_TEST_ARGS) ./... -coverprofile cover.out
|
||||
# Build manager binary
|
||||
manager: generate fmt vet
|
||||
go build -o bin/manager main.go
|
||||
|
||||
manager: generate fmt vet ## Build manager binary
|
||||
go build -o $(BUILD_DIR)/bin/manager ./main.go
|
||||
|
||||
run: generate fmt vet manifests # Run against the configured Kubernetes cluster in ~/.kube/config
|
||||
# Run against the configured Kubernetes cluster in ~/.kube/config
|
||||
run: generate fmt vet manifests
|
||||
go run ./main.go --log-level=${LOG_LEVEL} --log-encoding=console
|
||||
|
||||
install: manifests ## Install CRDs into a cluster
|
||||
# Install CRDs into a cluster
|
||||
install: manifests
|
||||
kustomize build config/crd | kubectl apply -f -
|
||||
|
||||
uninstall: manifests ## Uninstall CRDs from a cluster
|
||||
# Uninstall CRDs from a cluster
|
||||
uninstall: manifests
|
||||
kustomize build config/crd | kubectl delete -f -
|
||||
|
||||
deploy: manifests ## Deploy controller in the configured Kubernetes cluster in ~/.kube/config
|
||||
cd config/manager && kustomize edit set image fluxcd/image-automation-controller=$(IMG):$(TAG)
|
||||
# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
|
||||
deploy: manifests
|
||||
cd config/manager && kustomize edit set image fluxcd/image-automation-controller=${IMG}
|
||||
kustomize build config/default | kubectl apply -f -
|
||||
|
||||
dev-deploy: manifests
|
||||
mkdir -p config/dev && cp config/default/* config/dev
|
||||
cd config/dev && kustomize edit set image fluxcd/image-automation-controller=$(IMG):$(TAG)
|
||||
cd config/dev && kustomize edit set image fluxcd/image-automation-controller=${IMG}
|
||||
kustomize build config/dev | kubectl apply -f -
|
||||
rm -rf config/dev
|
||||
|
||||
manifests: controller-gen ## Generate manifests e.g. CRD, RBAC etc.
|
||||
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role paths="./..." output:crd:artifacts:config="config/crd/bases"
|
||||
# Generate manifests e.g. CRD, RBAC etc.
|
||||
manifests: controller-gen
|
||||
cd api; $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role paths="./..." output:crd:artifacts:config="../config/crd/bases"
|
||||
|
||||
api-docs: gen-crd-api-reference-docs ## Generate API reference documentation
|
||||
$(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v1beta2 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/v1beta2/image-automation.md
|
||||
# Generate API reference documentation
|
||||
api-docs: gen-crd-api-reference-docs
|
||||
$(API_REF_GEN) -api-dir=./api/v1beta1 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/image-automation.md
|
||||
|
||||
tidy: ## Run go mod tidy
|
||||
cd api; rm -f go.sum; go mod tidy -compat=1.25
|
||||
rm -f go.sum; go mod tidy -compat=1.25
|
||||
# Run go mod tidy
|
||||
tidy:
|
||||
cd api; rm -f go.sum; go mod tidy
|
||||
rm -f go.sum; go mod tidy
|
||||
|
||||
fmt: ## Run go fmt against code
|
||||
# Run go fmt against code
|
||||
fmt:
|
||||
go fmt ./...
|
||||
cd api; go fmt ./...
|
||||
|
||||
vet: ## Run go vet against code
|
||||
# Run go vet against code
|
||||
vet:
|
||||
go vet ./...
|
||||
cd api; go vet ./...
|
||||
|
||||
|
||||
generate: controller-gen ## Generate code
|
||||
# Generate code
|
||||
generate: controller-gen
|
||||
cd api; $(CONTROLLER_GEN) object:headerFile="../hack/boilerplate.go.txt" paths="./..."
|
||||
|
||||
docker-build: ## Build the Docker image
|
||||
docker buildx build \
|
||||
--platform=$(BUILD_PLATFORMS) \
|
||||
-t $(IMG):$(TAG) \
|
||||
$(BUILD_ARGS) .
|
||||
# Build the docker image
|
||||
docker-build: test
|
||||
docker build . -t ${IMG}
|
||||
|
||||
docker-push: ## Push the Docker image
|
||||
docker push $(IMG):$(TAG)
|
||||
# Push the docker image
|
||||
docker-push:
|
||||
docker push ${IMG}
|
||||
|
||||
docker-deploy: ## Set the Docker image in-cluster
|
||||
kubectl -n flux-system set image deployment/image-automation-controller manager=$(IMG):$(TAG)
|
||||
# Set the docker image in-cluster
|
||||
docker-deploy:
|
||||
kubectl -n flux-system set image deployment/image-automation-controller manager=${IMG}
|
||||
|
||||
# Find or download controller-gen
|
||||
CONTROLLER_GEN = $(GOBIN)/controller-gen
|
||||
.PHONY: controller-gen
|
||||
controller-gen: ## Download controller-gen locally if necessary.
|
||||
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION))
|
||||
|
||||
# Find or download gen-crd-api-reference-docs
|
||||
GEN_CRD_API_REFERENCE_DOCS = $(GOBIN)/gen-crd-api-reference-docs
|
||||
.PHONY: gen-crd-api-reference-docs
|
||||
gen-crd-api-reference-docs:
|
||||
$(call go-install-tool,$(GEN_CRD_API_REFERENCE_DOCS),github.com/ahmetb/gen-crd-api-reference-docs@$(GEN_API_REF_DOCS_VERSION))
|
||||
|
||||
ENVTEST_ASSETS_DIR=$(BUILD_DIR)/testbin
|
||||
ENVTEST_KUBERNETES_VERSION?=latest
|
||||
install-envtest: setup-envtest
|
||||
mkdir -p ${ENVTEST_ASSETS_DIR}
|
||||
$(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR)
|
||||
chmod -R u+w $(BUILD_DIR)/testbin
|
||||
|
||||
ENVTEST = $(GOBIN)/setup-envtest
|
||||
.PHONY: envtest
|
||||
setup-envtest: ## Download envtest-setup locally if necessary.
|
||||
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)
|
||||
|
||||
# Build fuzzers used by oss-fuzz.
|
||||
fuzz-build:
|
||||
rm -rf $(shell pwd)/build/fuzz/
|
||||
mkdir -p $(shell pwd)/build/fuzz/out/
|
||||
|
||||
docker build . --tag local-fuzzing:latest -f tests/fuzz/Dockerfile.builder
|
||||
docker run --rm \
|
||||
-e FUZZING_LANGUAGE=go -e SANITIZER=address \
|
||||
-e CIFUZZ_DEBUG='True' -e OSS_FUZZ_PROJECT_NAME=fluxcd \
|
||||
-v "$(shell pwd)/build/fuzz/out":/out \
|
||||
local-fuzzing:latest
|
||||
|
||||
# Run each fuzzer once to ensure they will work when executed by oss-fuzz.
|
||||
fuzz-smoketest: fuzz-build
|
||||
docker run --rm \
|
||||
-v "$(shell pwd)/build/fuzz/out":/out \
|
||||
-v "$(shell pwd)/tests/fuzz/oss_fuzz_run.sh":/runner.sh \
|
||||
local-fuzzing:latest \
|
||||
bash -c "/runner.sh"
|
||||
|
||||
# Run fuzz tests for the duration set in FUZZ_TIME.
|
||||
fuzz-native:
|
||||
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) \
|
||||
FUZZ_TIME=$(FUZZ_TIME) \
|
||||
./tests/fuzz/native_go_run.sh
|
||||
|
||||
# go-install-tool will 'go install' any package $2 and install it to $1.
|
||||
define go-install-tool
|
||||
@[ -f $(1) ] || { \
|
||||
set -e ;\
|
||||
TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
echo "Downloading $(2)" ;\
|
||||
env -i bash -c "GOBIN=$(GOBIN) PATH=$(PATH) GOPATH=$(shell go env GOPATH) GOCACHE=$(shell go env GOCACHE) go install $(2)" ;\
|
||||
rm -rf $$TMP_DIR ;\
|
||||
}
|
||||
endef
|
||||
|
||||
verify:
|
||||
ifneq (, $(shell git status --porcelain --untracked-files=no))
|
||||
# find or download controller-gen
|
||||
# download controller-gen if necessary
|
||||
controller-gen:
|
||||
ifeq (, $(shell which controller-gen))
|
||||
@{ \
|
||||
echo "working directory is dirty:"; \
|
||||
git --no-pager diff; \
|
||||
exit 1; \
|
||||
set -e ;\
|
||||
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$CONTROLLER_GEN_TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0 ;\
|
||||
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
|
||||
}
|
||||
CONTROLLER_GEN=$(GOBIN)/controller-gen
|
||||
else
|
||||
CONTROLLER_GEN=$(shell which controller-gen)
|
||||
endif
|
||||
|
||||
.PHONY: help
|
||||
help: ## Display this help menu
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
# Find or download gen-crd-api-reference-docs
|
||||
gen-crd-api-reference-docs:
|
||||
ifeq (, $(shell which gen-crd-api-reference-docs))
|
||||
@{ \
|
||||
set -e ;\
|
||||
API_REF_GEN_TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$API_REF_GEN_TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
go get github.com/ahmetb/gen-crd-api-reference-docs@v0.3.0 ;\
|
||||
rm -rf $$API_REF_GEN_TMP_DIR ;\
|
||||
}
|
||||
API_REF_GEN=$(GOBIN)/gen-crd-api-reference-docs
|
||||
else
|
||||
API_REF_GEN=$(shell which gen-crd-api-reference-docs)
|
||||
endif
|
||||
|
|
7
PROJECT
7
PROJECT
|
@ -3,8 +3,11 @@ repo: github.com/fluxcd/image-automation-controller
|
|||
resources:
|
||||
- group: image
|
||||
kind: ImageUpdateAutomation
|
||||
version: v1beta1
|
||||
version: v1alpha1
|
||||
- group: image
|
||||
kind: ImageUpdateAutomation
|
||||
version: v1beta2
|
||||
version: v1alpha2
|
||||
- group: image
|
||||
kind: ImageUpdateAutomation
|
||||
version: v1beta1
|
||||
version: "2"
|
||||
|
|
12
README.md
12
README.md
|
@ -17,9 +17,15 @@ updating YAML files in a git repository, and committing the changes.
|
|||
## How to install it
|
||||
|
||||
Please see the [installation and use
|
||||
guide](https://fluxcd.io/flux/guides/image-update/).
|
||||
guide](https://toolkit.fluxcd.io/guides/image-update/).
|
||||
|
||||
## How to work on it
|
||||
|
||||
For additional information on dependecies and how to contribute
|
||||
please refer to [DEVELOPMENT.md](DEVELOPMENT.md).
|
||||
The shared library `libgit2` needs to be installed to test or build
|
||||
locally. The version required corresponds to the version of git2go
|
||||
(which are Go bindings for libgit2), according to [this
|
||||
table](https://github.com/libgit2/git2go#which-go-version-to-use).
|
||||
|
||||
See
|
||||
https://github.com/fluxcd/source-controller/blob/main/CONTRIBUTING.md#installing-required-dependencies
|
||||
for instructions on how to install `libgit2`.
|
||||
|
|
34
api/go.mod
34
api/go.mod
|
@ -1,34 +1,10 @@
|
|||
module github.com/fluxcd/image-automation-controller/api
|
||||
|
||||
go 1.25.0
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/fluxcd/pkg/apis/meta v1.20.0
|
||||
github.com/fluxcd/source-controller/api v1.6.2
|
||||
k8s.io/apimachinery v0.34.0
|
||||
sigs.k8s.io/controller-runtime v0.22.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fluxcd/pkg/apis/acl v0.7.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/spf13/pflag v1.0.7 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||
github.com/fluxcd/pkg/apis/meta v0.10.0
|
||||
github.com/fluxcd/source-controller/api v0.15.4
|
||||
k8s.io/apimachinery v0.21.3
|
||||
sigs.k8s.io/controller-runtime v0.9.5
|
||||
)
|
||||
|
|
732
api/go.sum
732
api/go.sum
|
@ -1,116 +1,710 @@
|
|||
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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
|
||||
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=
|
||||
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
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/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
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.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
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/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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/fluxcd/pkg/apis/acl v0.7.0 h1:dMhZJH+g6ZRPjs4zVOAN9vHBd1DcavFgcIFkg5ooOE0=
|
||||
github.com/fluxcd/pkg/apis/acl v0.7.0/go.mod h1:uv7pXXR/gydiX4MUwlQa7vS8JONEDztynnjTvY3JxKQ=
|
||||
github.com/fluxcd/pkg/apis/meta v1.20.0 h1:l9h0kWoDZTcYV0WJkFMgDXq6Q4tSojrJ+bHpFJSsaW0=
|
||||
github.com/fluxcd/pkg/apis/meta v1.20.0/go.mod h1:XUAEUgT4gkWDAEN79E141tmL+v4SV50tVZ/Ojpc/ueg=
|
||||
github.com/fluxcd/source-controller/api v1.6.2 h1:UmodAeqLIeF29HdTqf2GiacZyO+hJydJlepDaYsMvhc=
|
||||
github.com/fluxcd/source-controller/api v1.6.2/go.mod h1:ZJcAi0nemsnBxjVgmJl0WQzNvB0rMETxQMTdoFosmMw=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fluxcd/pkg/apis/meta v0.10.0 h1:N7wVGHC1cyPdT87hrDC7UwCwRwnZdQM46PBSLjG2rlE=
|
||||
github.com/fluxcd/pkg/apis/meta v0.10.0/go.mod h1:CW9X9ijMTpNe7BwnokiUOrLl/h13miwVr/3abEQLbKE=
|
||||
github.com/fluxcd/source-controller/api v0.15.4 h1:9aRcH/WKJWt7Bp954K/wzLRuiRiHuD2osvYp74GoP64=
|
||||
github.com/fluxcd/source-controller/api v0.15.4/go.mod h1:guUCCapjzE2kocwFreQTM/IGvtAglIJc4L97mokairo=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
|
||||
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
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/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
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.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
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/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
||||
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=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
|
||||
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
|
||||
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI=
|
||||
github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
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/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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
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.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
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/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
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.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
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/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
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-20190227155943-e225da77a7e6/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
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=
|
||||
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE=
|
||||
k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug=
|
||||
k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0=
|
||||
k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0=
|
||||
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/controller-runtime v0.22.0 h1:mTOfibb8Hxwpx3xEkR56i7xSjB+nH4hZG37SrlCY5e0=
|
||||
sigs.k8s.io/controller-runtime v0.22.0/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
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/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/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.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.21.3 h1:cblWILbLO8ar+Fj6xdDGr603HRsf8Wu9E9rngJeprZQ=
|
||||
k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg=
|
||||
k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE=
|
||||
k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
|
||||
k8s.io/apimachinery v0.21.3 h1:3Ju4nvjCngxxMYby0BimUk+pQHPOQp3eCGChk5kfVII=
|
||||
k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI=
|
||||
k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU=
|
||||
k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU=
|
||||
k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo=
|
||||
k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ=
|
||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||
k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts=
|
||||
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
||||
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
|
||||
sigs.k8s.io/controller-runtime v0.9.5 h1:WThcFE6cqctTn2jCZprLICO6BaKZfhsT37uAapTNfxc=
|
||||
sigs.k8s.io/controller-runtime v0.9.5/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2024 The Flux authors
|
||||
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.
|
||||
|
@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package v1beta2 contains API types for the image API group, version
|
||||
// v1beta2. The types here are concerned with automated updates to
|
||||
// git, based on metadata from OCI image registries gathered by the
|
||||
// Package v1alpha1 contains API types for the image v1alpha1 API
|
||||
// group. The types here are concerned with automated updates to git,
|
||||
// based on metadata from OCI image registries gathered by the
|
||||
// image-reflector-controller.
|
||||
//
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=image.toolkit.fluxcd.io
|
||||
package v1beta2
|
||||
package v1alpha1
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2024 The Flux authors
|
||||
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.
|
||||
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package v1beta2 contains API Schema definitions for the image v1beta2 API group
|
||||
// Package v1alpha1 contains API Schema definitions for the image v1alpha1 API group
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=image.toolkit.fluxcd.io
|
||||
package v1beta2
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
var (
|
||||
// GroupVersion is group version used to register these objects
|
||||
GroupVersion = schema.GroupVersion{Group: "image.toolkit.fluxcd.io", Version: "v1beta2"}
|
||||
GroupVersion = schema.GroupVersion{Group: "image.toolkit.fluxcd.io", Version: "v1alpha1"}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
)
|
||||
|
||||
const ImageUpdateAutomationKind = "ImageUpdateAutomation"
|
||||
|
||||
// ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation
|
||||
type ImageUpdateAutomationSpec struct {
|
||||
// Checkout gives the parameters for cloning the git repository,
|
||||
// ready to make changes.
|
||||
// +required
|
||||
Checkout GitCheckoutSpec `json:"checkout"`
|
||||
|
||||
// Interval gives an lower bound for how often the automation
|
||||
// run should be attempted.
|
||||
// +required
|
||||
Interval metav1.Duration `json:"interval"`
|
||||
|
||||
// Update gives the specification for how to update the files in
|
||||
// the repository. This can be left empty, to use the default
|
||||
// value.
|
||||
// +kubebuilder:default={"strategy":"Setters"}
|
||||
Update *UpdateStrategy `json:"update,omitempty"`
|
||||
|
||||
// Commit specifies how to commit to the git repository.
|
||||
// +required
|
||||
Commit CommitSpec `json:"commit"`
|
||||
|
||||
// Push specifies how and where to push commits made by the
|
||||
// automation. If missing, commits are pushed (back) to
|
||||
// `.spec.checkout.branch`.
|
||||
// +optional
|
||||
Push *PushSpec `json:"push,omitempty"`
|
||||
|
||||
// Suspend tells the controller to not run this automation, until
|
||||
// it is unset (or set to false). Defaults to false.
|
||||
// +optional
|
||||
Suspend bool `json:"suspend,omitempty"`
|
||||
}
|
||||
|
||||
type GitCheckoutSpec struct {
|
||||
// GitRepositoryRef refers to the resource giving access details
|
||||
// to a git repository to update files in.
|
||||
// +required
|
||||
GitRepositoryRef meta.LocalObjectReference `json:"gitRepositoryRef"`
|
||||
// Branch gives the branch to clone from the git repository. If
|
||||
// `.spec.push` is not supplied, commits will also be pushed to
|
||||
// this branch.
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
|
||||
// UpdateStrategyName is the type for names that go in
|
||||
// .update.strategy. NB the value in the const immediately below.
|
||||
// +kubebuilder:validation:Enum=Setters
|
||||
type UpdateStrategyName string
|
||||
|
||||
const (
|
||||
// UpdateStrategySetters is the name of the update strategy that
|
||||
// uses kyaml setters. NB the value in the enum annotation for the
|
||||
// type, above.
|
||||
UpdateStrategySetters UpdateStrategyName = "Setters"
|
||||
)
|
||||
|
||||
// UpdateStrategy is a union of the various strategies for updating
|
||||
// the Git repository. Parameters for each strategy (if any) can be
|
||||
// inlined here.
|
||||
type UpdateStrategy struct {
|
||||
// Strategy names the strategy to be used.
|
||||
// +required
|
||||
// +kubebuilder:default=Setters
|
||||
Strategy UpdateStrategyName `json:"strategy"`
|
||||
|
||||
// Path to the directory containing the manifests to be updated.
|
||||
// Defaults to 'None', which translates to the root path
|
||||
// of the GitRepositoryRef.
|
||||
// +optional
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// CommitSpec specifies how to commit changes to the git repository
|
||||
type CommitSpec struct {
|
||||
// AuthorName gives the name to provide when making a commit
|
||||
// +required
|
||||
AuthorName string `json:"authorName"`
|
||||
// AuthorEmail gives the email to provide when making a commit
|
||||
// +required
|
||||
AuthorEmail string `json:"authorEmail"`
|
||||
// SigningKey provides the option to sign commits with a GPG key
|
||||
// +optional
|
||||
SigningKey *SigningKey `json:"signingKey,omitempty"`
|
||||
// MessageTemplate provides a template for the commit message,
|
||||
// into which will be interpolated the details of the change made.
|
||||
// +optional
|
||||
MessageTemplate string `json:"messageTemplate,omitempty"`
|
||||
}
|
||||
|
||||
// PushSpec specifies how and where to push commits.
|
||||
type PushSpec struct {
|
||||
// Branch specifies that commits should be pushed to the branch
|
||||
// named. The branch is created using `.spec.checkout.branch` as the
|
||||
// starting point, if it doesn't already exist.
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
|
||||
// ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation
|
||||
type ImageUpdateAutomationStatus struct {
|
||||
// LastAutomationRunTime records the last time the controller ran
|
||||
// this automation through to completion (even if no updates were
|
||||
// made).
|
||||
// +optional
|
||||
LastAutomationRunTime *metav1.Time `json:"lastAutomationRunTime,omitempty"`
|
||||
// LastPushCommit records the SHA1 of the last commit made by the
|
||||
// controller, for this automation object
|
||||
// +optional
|
||||
LastPushCommit string `json:"lastPushCommit,omitempty"`
|
||||
// LastPushTime records the time of the last pushed change.
|
||||
// +optional
|
||||
LastPushTime *metav1.Time `json:"lastPushTime,omitempty"`
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
meta.ReconcileRequestStatus `json:",inline"`
|
||||
}
|
||||
|
||||
// SigningKey references a Kubernetes secret that contains a GPG keypair
|
||||
type SigningKey struct {
|
||||
// SecretRef holds the name to a secret that contains a 'git.asc' key
|
||||
// corresponding to the ASCII Armored file containing the GPG signing
|
||||
// keypair as the value. It must be in the same namespace as the
|
||||
// ImageUpdateAutomation.
|
||||
// +required
|
||||
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
// GitNotAvailableReason is used for ConditionReady when the
|
||||
// automation run cannot proceed because the git repository is
|
||||
// missing or cannot be cloned.
|
||||
GitNotAvailableReason = "GitRepositoryNotAvailable"
|
||||
// NoStrategyReason is used for ConditionReady when the automation
|
||||
// run cannot proceed because there is no update strategy given in
|
||||
// the spec.
|
||||
NoStrategyReason = "MissingUpdateStrategy"
|
||||
)
|
||||
|
||||
// SetImageUpdateAutomationReadiness sets the ready condition with the given status, reason and message.
|
||||
func SetImageUpdateAutomationReadiness(auto *ImageUpdateAutomation, status metav1.ConditionStatus, reason, message string) {
|
||||
auto.Status.ObservedGeneration = auto.ObjectMeta.Generation
|
||||
meta.SetResourceCondition(auto, meta.ReadyCondition, status, reason, message)
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="Last run",type=string,JSONPath=`.status.lastAutomationRunTime`
|
||||
|
||||
// ImageUpdateAutomation is the Schema for the imageupdateautomations API
|
||||
type ImageUpdateAutomation struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ImageUpdateAutomationSpec `json:"spec,omitempty"`
|
||||
Status ImageUpdateAutomationStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (auto *ImageUpdateAutomation) GetStatusConditions() *[]metav1.Condition {
|
||||
return &auto.Status.Conditions
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// ImageUpdateAutomationList contains a list of ImageUpdateAutomation
|
||||
type ImageUpdateAutomationList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []ImageUpdateAutomation `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&ImageUpdateAutomation{}, &ImageUpdateAutomationList{})
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CommitSpec) DeepCopyInto(out *CommitSpec) {
|
||||
*out = *in
|
||||
if in.SigningKey != nil {
|
||||
in, out := &in.SigningKey, &out.SigningKey
|
||||
*out = new(SigningKey)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommitSpec.
|
||||
func (in *CommitSpec) DeepCopy() *CommitSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CommitSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GitCheckoutSpec) DeepCopyInto(out *GitCheckoutSpec) {
|
||||
*out = *in
|
||||
out.GitRepositoryRef = in.GitRepositoryRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitCheckoutSpec.
|
||||
func (in *GitCheckoutSpec) DeepCopy() *GitCheckoutSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GitCheckoutSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageUpdateAutomation) DeepCopyInto(out *ImageUpdateAutomation) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageUpdateAutomation.
|
||||
func (in *ImageUpdateAutomation) DeepCopy() *ImageUpdateAutomation {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageUpdateAutomation)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ImageUpdateAutomation) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageUpdateAutomationList) DeepCopyInto(out *ImageUpdateAutomationList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]ImageUpdateAutomation, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageUpdateAutomationList.
|
||||
func (in *ImageUpdateAutomationList) DeepCopy() *ImageUpdateAutomationList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageUpdateAutomationList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ImageUpdateAutomationList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageUpdateAutomationSpec) DeepCopyInto(out *ImageUpdateAutomationSpec) {
|
||||
*out = *in
|
||||
out.Checkout = in.Checkout
|
||||
out.Interval = in.Interval
|
||||
if in.Update != nil {
|
||||
in, out := &in.Update, &out.Update
|
||||
*out = new(UpdateStrategy)
|
||||
**out = **in
|
||||
}
|
||||
in.Commit.DeepCopyInto(&out.Commit)
|
||||
if in.Push != nil {
|
||||
in, out := &in.Push, &out.Push
|
||||
*out = new(PushSpec)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageUpdateAutomationSpec.
|
||||
func (in *ImageUpdateAutomationSpec) DeepCopy() *ImageUpdateAutomationSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageUpdateAutomationSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageUpdateAutomationStatus) DeepCopyInto(out *ImageUpdateAutomationStatus) {
|
||||
*out = *in
|
||||
if in.LastAutomationRunTime != nil {
|
||||
in, out := &in.LastAutomationRunTime, &out.LastAutomationRunTime
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.LastPushTime != nil {
|
||||
in, out := &in.LastPushTime, &out.LastPushTime
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
out.ReconcileRequestStatus = in.ReconcileRequestStatus
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageUpdateAutomationStatus.
|
||||
func (in *ImageUpdateAutomationStatus) DeepCopy() *ImageUpdateAutomationStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageUpdateAutomationStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSpec) DeepCopyInto(out *PushSpec) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSpec.
|
||||
func (in *PushSpec) DeepCopy() *PushSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SigningKey) DeepCopyInto(out *SigningKey) {
|
||||
*out = *in
|
||||
out.SecretRef = in.SecretRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SigningKey.
|
||||
func (in *SigningKey) DeepCopy() *SigningKey {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SigningKey)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UpdateStrategy) DeepCopyInto(out *UpdateStrategy) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpdateStrategy.
|
||||
func (in *UpdateStrategy) DeepCopy() *UpdateStrategy {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(UpdateStrategy)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
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.
|
||||
|
@ -14,11 +14,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package constants
|
||||
|
||||
const (
|
||||
// SetterShortHand is a shorthand that can be used to mark
|
||||
// setters; instead of
|
||||
// # { "$ref": "#/definitions/
|
||||
SetterShortHand = "$imagepolicy"
|
||||
)
|
||||
// Package v1alpha2 contains API types for the image v1alpha2 API
|
||||
// group. The types here are concerned with automated updates to git,
|
||||
// based on metadata from OCI image registries gathered by the
|
||||
// image-reflector-controller. There is some rearrangement from
|
||||
// v1alpha1 to make room for future enhancements.
|
||||
//
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=image.toolkit.fluxcd.io
|
||||
package v1alpha2
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2024 The Flux authors
|
||||
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.
|
||||
|
@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1beta2
|
||||
package v1alpha2
|
||||
|
||||
import (
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
)
|
||||
|
||||
type GitSpec struct {
|
||||
|
@ -39,14 +39,6 @@ type GitSpec struct {
|
|||
Push *PushSpec `json:"push,omitempty"`
|
||||
}
|
||||
|
||||
// HasRefspec returns if the GitSpec has a Refspec.
|
||||
func (gs GitSpec) HasRefspec() bool {
|
||||
if gs.Push == nil {
|
||||
return false
|
||||
}
|
||||
return gs.Push.Refspec != ""
|
||||
}
|
||||
|
||||
type GitCheckoutSpec struct {
|
||||
// Reference gives a branch, tag or commit to clone from the Git
|
||||
// repository.
|
||||
|
@ -65,14 +57,8 @@ type CommitSpec struct {
|
|||
SigningKey *SigningKey `json:"signingKey,omitempty"`
|
||||
// MessageTemplate provides a template for the commit message,
|
||||
// into which will be interpolated the details of the change made.
|
||||
// Note: The `Updated` template field has been removed. Use `Changed` instead.
|
||||
// +optional
|
||||
MessageTemplate string `json:"messageTemplate,omitempty"`
|
||||
|
||||
// MessageTemplateValues provides additional values to be available to the
|
||||
// templating rendering.
|
||||
// +optional
|
||||
MessageTemplateValues map[string]string `json:"messageTemplateValues,omitempty"`
|
||||
}
|
||||
|
||||
type CommitUser struct {
|
||||
|
@ -99,20 +85,6 @@ type PushSpec struct {
|
|||
// Branch specifies that commits should be pushed to the branch
|
||||
// named. The branch is created using `.spec.checkout.branch` as the
|
||||
// starting point, if it doesn't already exist.
|
||||
// +optional
|
||||
Branch string `json:"branch,omitempty"`
|
||||
|
||||
// Refspec specifies the Git Refspec to use for a push operation.
|
||||
// If both Branch and Refspec are provided, then the commit is pushed
|
||||
// to the branch and also using the specified refspec.
|
||||
// For more details about Git Refspecs, see:
|
||||
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
|
||||
// +optional
|
||||
Refspec string `json:"refspec,omitempty"`
|
||||
|
||||
// Options specifies the push options that are sent to the Git
|
||||
// server when performing a push operation. For details, see:
|
||||
// https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt
|
||||
// +optional
|
||||
Options map[string]string `json:"options,omitempty"`
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
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 v1alpha2
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is group version used to register these objects
|
||||
GroupVersion = schema.GroupVersion{Group: "image.toolkit.fluxcd.io", Version: "v1alpha2"}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||
|
||||
// AddToScheme adds the types in this group-version to the given scheme.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2024 The Flux authors
|
||||
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.
|
||||
|
@ -14,28 +14,22 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1beta2
|
||||
package v1alpha2
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
)
|
||||
|
||||
const (
|
||||
ImageUpdateAutomationKind = "ImageUpdateAutomation"
|
||||
ImageUpdateAutomationFinalizer = "finalizers.fluxcd.io"
|
||||
)
|
||||
const ImageUpdateAutomationKind = "ImageUpdateAutomation"
|
||||
|
||||
// ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation
|
||||
type ImageUpdateAutomationSpec struct {
|
||||
// SourceRef refers to the resource giving access details
|
||||
// to a git repository.
|
||||
// +required
|
||||
SourceRef CrossNamespaceSourceReference `json:"sourceRef"`
|
||||
|
||||
SourceRef SourceReference `json:"sourceRef"`
|
||||
// GitSpec contains all the git-specific definitions. This is
|
||||
// technically optional, but in practice mandatory until there are
|
||||
// other kinds of source allowed.
|
||||
|
@ -44,16 +38,9 @@ type ImageUpdateAutomationSpec struct {
|
|||
|
||||
// Interval gives an lower bound for how often the automation
|
||||
// run should be attempted.
|
||||
// +kubebuilder:validation:Type=string
|
||||
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
|
||||
// +required
|
||||
Interval metav1.Duration `json:"interval"`
|
||||
|
||||
// PolicySelector allows to filter applied policies based on labels.
|
||||
// By default includes all policies in namespace.
|
||||
// +optional
|
||||
PolicySelector *metav1.LabelSelector `json:"policySelector,omitempty"`
|
||||
|
||||
// Update gives the specification for how to update the files in
|
||||
// the repository. This can be left empty, to use the default
|
||||
// value.
|
||||
|
@ -83,9 +70,9 @@ const (
|
|||
// inlined here.
|
||||
type UpdateStrategy struct {
|
||||
// Strategy names the strategy to be used.
|
||||
// +optional
|
||||
// +required
|
||||
// +kubebuilder:default=Setters
|
||||
Strategy UpdateStrategyName `json:"strategy,omitempty"`
|
||||
Strategy UpdateStrategyName `json:"strategy"`
|
||||
|
||||
// Path to the directory containing the manifests to be updated.
|
||||
// Defaults to 'None', which translates to the root path
|
||||
|
@ -111,57 +98,42 @@ type ImageUpdateAutomationStatus struct {
|
|||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
// ObservedPolicies is the list of observed ImagePolicies that were
|
||||
// considered by the ImageUpdateAutomation update process.
|
||||
// +optional
|
||||
ObservedPolicies ObservedPolicies `json:"observedPolicies,omitempty"`
|
||||
// ObservedPolicies []ObservedPolicy `json:"observedPolicies,omitempty"`
|
||||
// ObservedSourceRevision is the last observed source revision. This can be
|
||||
// used to determine if the source has been updated since last observation.
|
||||
// +optional
|
||||
ObservedSourceRevision string `json:"observedSourceRevision,omitempty"`
|
||||
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
meta.ReconcileRequestStatus `json:",inline"`
|
||||
}
|
||||
|
||||
// ObservedPolicies is a map of policy name and ImageRef of their latest
|
||||
// ImageRef.
|
||||
type ObservedPolicies map[string]ImageRef
|
||||
const (
|
||||
// GitNotAvailableReason is used for ConditionReady when the
|
||||
// automation run cannot proceed because the git repository is
|
||||
// missing or cannot be cloned.
|
||||
GitNotAvailableReason = "GitRepositoryNotAvailable"
|
||||
// NoStrategyReason is used for ConditionReady when the automation
|
||||
// run cannot proceed because there is no update strategy given in
|
||||
// the spec.
|
||||
NoStrategyReason = "MissingUpdateStrategy"
|
||||
)
|
||||
|
||||
// SetImageUpdateAutomationReadiness sets the ready condition with the given status, reason and message.
|
||||
func SetImageUpdateAutomationReadiness(auto *ImageUpdateAutomation, status metav1.ConditionStatus, reason, message string) {
|
||||
auto.Status.ObservedGeneration = auto.ObjectMeta.Generation
|
||||
meta.SetResourceCondition(auto, meta.ReadyCondition, status, reason, message)
|
||||
}
|
||||
|
||||
//+kubebuilder:storageversion
|
||||
//+kubebuilder:object:root=true
|
||||
//+kubebuilder:subresource:status
|
||||
//+kubebuilder:resource:shortName=iua;imgupd;imgauto
|
||||
//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
|
||||
//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description=""
|
||||
//+kubebuilder:printcolumn:name="Last run",type="string",JSONPath=".status.lastAutomationRunTime",priority=1
|
||||
//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
|
||||
//+kubebuilder:printcolumn:name="Last run",type=string,JSONPath=`.status.lastAutomationRunTime`
|
||||
|
||||
// ImageUpdateAutomation is the Schema for the imageupdateautomations API
|
||||
type ImageUpdateAutomation struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ImageUpdateAutomationSpec `json:"spec,omitempty"`
|
||||
// +kubebuilder:default={"observedGeneration":-1}
|
||||
Spec ImageUpdateAutomationSpec `json:"spec,omitempty"`
|
||||
Status ImageUpdateAutomationStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// GetRequeueAfter returns the duration after which the ImageUpdateAutomation
|
||||
// must be reconciled again.
|
||||
func (auto ImageUpdateAutomation) GetRequeueAfter() time.Duration {
|
||||
return auto.Spec.Interval.Duration
|
||||
}
|
||||
|
||||
// GetConditions returns the status conditions of the object.
|
||||
func (auto ImageUpdateAutomation) GetConditions() []metav1.Condition {
|
||||
return auto.Status.Conditions
|
||||
}
|
||||
|
||||
// SetConditions sets the status conditions on the object.
|
||||
func (auto *ImageUpdateAutomation) SetConditions(conditions []metav1.Condition) {
|
||||
auto.Status.Conditions = conditions
|
||||
func (auto *ImageUpdateAutomation) GetStatusConditions() *[]metav1.Condition {
|
||||
return &auto.Status.Conditions
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
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.
|
||||
|
@ -14,14 +14,22 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1beta1
|
||||
package v1alpha2
|
||||
|
||||
const (
|
||||
// ReconciliationSucceededReason represents the fact that
|
||||
// the reconciliation succeeded.
|
||||
ReconciliationSucceededReason string = "ReconciliationSucceeded"
|
||||
// SourceReference contains enough information to let you locate the
|
||||
// typed, referenced source object.
|
||||
type SourceReference struct {
|
||||
// API version of the referent
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// ReconciliationFailedReason represents the fact that
|
||||
// the reconciliation failed.
|
||||
ReconciliationFailedReason string = "ReconciliationFailed"
|
||||
)
|
||||
// Kind of the referent
|
||||
// +kubebuilder:validation:Enum=GitRepository
|
||||
// +kubebuilder:default=GitRepository
|
||||
// +required
|
||||
Kind string `json:"kind"`
|
||||
|
||||
// Name of the referent
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
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.
|
||||
|
@ -18,7 +18,7 @@ limitations under the License.
|
|||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta2
|
||||
package v1alpha2
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -34,13 +34,6 @@ func (in *CommitSpec) DeepCopyInto(out *CommitSpec) {
|
|||
*out = new(SigningKey)
|
||||
**out = **in
|
||||
}
|
||||
if in.MessageTemplateValues != nil {
|
||||
in, out := &in.MessageTemplateValues, &out.MessageTemplateValues
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommitSpec.
|
||||
|
@ -68,21 +61,6 @@ func (in *CommitUser) DeepCopy() *CommitUser {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CrossNamespaceSourceReference) DeepCopyInto(out *CrossNamespaceSourceReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossNamespaceSourceReference.
|
||||
func (in *CrossNamespaceSourceReference) DeepCopy() *CrossNamespaceSourceReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CrossNamespaceSourceReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GitCheckoutSpec) DeepCopyInto(out *GitCheckoutSpec) {
|
||||
*out = *in
|
||||
|
@ -111,7 +89,7 @@ func (in *GitSpec) DeepCopyInto(out *GitSpec) {
|
|||
if in.Push != nil {
|
||||
in, out := &in.Push, &out.Push
|
||||
*out = new(PushSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,21 +103,6 @@ func (in *GitSpec) DeepCopy() *GitSpec {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageRef) DeepCopyInto(out *ImageRef) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageRef.
|
||||
func (in *ImageRef) DeepCopy() *ImageRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageUpdateAutomation) DeepCopyInto(out *ImageUpdateAutomation) {
|
||||
*out = *in
|
||||
|
@ -209,11 +172,6 @@ func (in *ImageUpdateAutomationSpec) DeepCopyInto(out *ImageUpdateAutomationSpec
|
|||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
out.Interval = in.Interval
|
||||
if in.PolicySelector != nil {
|
||||
in, out := &in.PolicySelector, &out.PolicySelector
|
||||
*out = new(v1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Update != nil {
|
||||
in, out := &in.Update, &out.Update
|
||||
*out = new(UpdateStrategy)
|
||||
|
@ -249,13 +207,6 @@ func (in *ImageUpdateAutomationStatus) DeepCopyInto(out *ImageUpdateAutomationSt
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ObservedPolicies != nil {
|
||||
in, out := &in.ObservedPolicies, &out.ObservedPolicies
|
||||
*out = make(ObservedPolicies, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
out.ReconcileRequestStatus = in.ReconcileRequestStatus
|
||||
}
|
||||
|
||||
|
@ -269,37 +220,9 @@ func (in *ImageUpdateAutomationStatus) DeepCopy() *ImageUpdateAutomationStatus {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ObservedPolicies) DeepCopyInto(out *ObservedPolicies) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(ObservedPolicies, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObservedPolicies.
|
||||
func (in ObservedPolicies) DeepCopy() ObservedPolicies {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ObservedPolicies)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSpec) DeepCopyInto(out *PushSpec) {
|
||||
*out = *in
|
||||
if in.Options != nil {
|
||||
in, out := &in.Options, &out.Options
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSpec.
|
||||
|
@ -328,6 +251,21 @@ func (in *SigningKey) DeepCopy() *SigningKey {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SourceReference) DeepCopyInto(out *SourceReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceReference.
|
||||
func (in *SourceReference) DeepCopy() *SourceReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SourceReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UpdateStrategy) DeepCopyInto(out *UpdateStrategy) {
|
||||
*out = *in
|
|
@ -14,9 +14,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package v1beta1 contains API types for the image v1beta1 API group
|
||||
//
|
||||
// Deprecated: v1beta1 is no longer supported, use v1 instead.
|
||||
// Package v1beta1 contains API types for the image API group, version
|
||||
// v1beta1. The types here are concerned with automated updates to
|
||||
// git, based on metadata from OCI image registries gathered by the
|
||||
// image-reflector-controller. v1alpha2 did some rearrangement from
|
||||
// v1alpha1 to make room for future enhancements; v1beta1 does not
|
||||
// change the schema from v1alpha2.
|
||||
//
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=image.toolkit.fluxcd.io
|
||||
|
|
|
@ -18,7 +18,7 @@ package v1beta1
|
|||
|
||||
import (
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
)
|
||||
|
||||
type GitSpec struct {
|
||||
|
@ -85,20 +85,6 @@ type PushSpec struct {
|
|||
// Branch specifies that commits should be pushed to the branch
|
||||
// named. The branch is created using `.spec.checkout.branch` as the
|
||||
// starting point, if it doesn't already exist.
|
||||
// +optional
|
||||
Branch string `json:"branch,omitempty"`
|
||||
|
||||
// Refspec specifies the Git Refspec to use for a push operation.
|
||||
// If both Branch and Refspec are provided, then the commit is pushed
|
||||
// to the branch and also using the specified refspec.
|
||||
// For more details about Git Refspecs, see:
|
||||
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
|
||||
// +optional
|
||||
Refspec string `json:"refspec,omitempty"`
|
||||
|
||||
// Options specifies the push options that are sent to the Git
|
||||
// server when performing a push operation. For details, see:
|
||||
// https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt
|
||||
// +optional
|
||||
Options map[string]string `json:"options,omitempty"`
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
|
|
|
@ -17,25 +17,19 @@ limitations under the License.
|
|||
package v1beta1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
)
|
||||
|
||||
const (
|
||||
ImageUpdateAutomationKind = "ImageUpdateAutomation"
|
||||
ImageUpdateAutomationFinalizer = "finalizers.fluxcd.io"
|
||||
)
|
||||
const ImageUpdateAutomationKind = "ImageUpdateAutomation"
|
||||
|
||||
// ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation
|
||||
type ImageUpdateAutomationSpec struct {
|
||||
// SourceRef refers to the resource giving access details
|
||||
// to a git repository.
|
||||
// +required
|
||||
SourceRef CrossNamespaceSourceReference `json:"sourceRef"`
|
||||
SourceRef SourceReference `json:"sourceRef"`
|
||||
// GitSpec contains all the git-specific definitions. This is
|
||||
// technically optional, but in practice mandatory until there are
|
||||
// other kinds of source allowed.
|
||||
|
@ -44,8 +38,6 @@ type ImageUpdateAutomationSpec struct {
|
|||
|
||||
// Interval gives an lower bound for how often the automation
|
||||
// run should be attempted.
|
||||
// +kubebuilder:validation:Type=string
|
||||
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
|
||||
// +required
|
||||
Interval metav1.Duration `json:"interval"`
|
||||
|
||||
|
@ -124,46 +116,23 @@ const (
|
|||
// SetImageUpdateAutomationReadiness sets the ready condition with the given status, reason and message.
|
||||
func SetImageUpdateAutomationReadiness(auto *ImageUpdateAutomation, status metav1.ConditionStatus, reason, message string) {
|
||||
auto.Status.ObservedGeneration = auto.ObjectMeta.Generation
|
||||
newCondition := metav1.Condition{
|
||||
Type: meta.ReadyCondition,
|
||||
Status: status,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
}
|
||||
apimeta.SetStatusCondition(auto.GetStatusConditions(), newCondition)
|
||||
meta.SetResourceCondition(auto, meta.ReadyCondition, status, reason, message)
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:skipversion
|
||||
//+kubebuilder:storageversion
|
||||
//+kubebuilder:object:root=true
|
||||
//+kubebuilder:subresource:status
|
||||
//+kubebuilder:printcolumn:name="Last run",type=string,JSONPath=`.status.lastAutomationRunTime`
|
||||
|
||||
// ImageUpdateAutomation is the Schema for the imageupdateautomations API
|
||||
type ImageUpdateAutomation struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ImageUpdateAutomationSpec `json:"spec,omitempty"`
|
||||
// +kubebuilder:default={"observedGeneration":-1}
|
||||
Spec ImageUpdateAutomationSpec `json:"spec,omitempty"`
|
||||
Status ImageUpdateAutomationStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// GetRequeueAfter returns the duration after which the ImageUpdateAutomation
|
||||
// must be reconciled again.
|
||||
func (auto ImageUpdateAutomation) GetRequeueAfter() time.Duration {
|
||||
return auto.Spec.Interval.Duration
|
||||
}
|
||||
|
||||
// GetConditions returns the status conditions of the object.
|
||||
func (auto ImageUpdateAutomation) GetConditions() []metav1.Condition {
|
||||
return auto.Status.Conditions
|
||||
}
|
||||
|
||||
// SetConditions sets the status conditions on the object.
|
||||
func (auto *ImageUpdateAutomation) SetConditions(conditions []metav1.Condition) {
|
||||
auto.Status.Conditions = conditions
|
||||
}
|
||||
|
||||
// GetStatusConditions returns a pointer to the Status.Conditions slice.
|
||||
// Deprecated: use GetConditions instead.
|
||||
func (auto *ImageUpdateAutomation) GetStatusConditions() *[]metav1.Condition {
|
||||
return &auto.Status.Conditions
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020, 2021 The Flux authors
|
||||
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.
|
||||
|
@ -16,33 +16,20 @@ limitations under the License.
|
|||
|
||||
package v1beta1
|
||||
|
||||
import "fmt"
|
||||
|
||||
// CrossNamespaceSourceReference contains enough information to let you locate the
|
||||
// typed Kubernetes resource object at cluster level.
|
||||
type CrossNamespaceSourceReference struct {
|
||||
// API version of the referent.
|
||||
// SourceReference contains enough information to let you locate the
|
||||
// typed, referenced source object.
|
||||
type SourceReference struct {
|
||||
// API version of the referent
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// Kind of the referent.
|
||||
// Kind of the referent
|
||||
// +kubebuilder:validation:Enum=GitRepository
|
||||
// +kubebuilder:default=GitRepository
|
||||
// +required
|
||||
Kind string `json:"kind"`
|
||||
|
||||
// Name of the referent.
|
||||
// Name of the referent
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
|
||||
// Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
func (s *CrossNamespaceSourceReference) String() string {
|
||||
if s.Namespace != "" {
|
||||
return fmt.Sprintf("%s/%s/%s", s.Kind, s.Namespace, s.Name)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", s.Kind, s.Name)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
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.
|
||||
|
@ -61,21 +61,6 @@ func (in *CommitUser) DeepCopy() *CommitUser {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CrossNamespaceSourceReference) DeepCopyInto(out *CrossNamespaceSourceReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossNamespaceSourceReference.
|
||||
func (in *CrossNamespaceSourceReference) DeepCopy() *CrossNamespaceSourceReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CrossNamespaceSourceReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GitCheckoutSpec) DeepCopyInto(out *GitCheckoutSpec) {
|
||||
*out = *in
|
||||
|
@ -104,7 +89,7 @@ func (in *GitSpec) DeepCopyInto(out *GitSpec) {
|
|||
if in.Push != nil {
|
||||
in, out := &in.Push, &out.Push
|
||||
*out = new(PushSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,13 +223,6 @@ func (in *ImageUpdateAutomationStatus) DeepCopy() *ImageUpdateAutomationStatus {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSpec) DeepCopyInto(out *PushSpec) {
|
||||
*out = *in
|
||||
if in.Options != nil {
|
||||
in, out := &in.Options, &out.Options
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSpec.
|
||||
|
@ -273,6 +251,21 @@ func (in *SigningKey) DeepCopy() *SigningKey {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SourceReference) DeepCopyInto(out *SourceReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceReference.
|
||||
func (in *SourceReference) DeepCopy() *SourceReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SourceReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UpdateStrategy) DeepCopyInto(out *UpdateStrategy) {
|
||||
*out = *in
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
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 v1beta2
|
||||
|
||||
const (
|
||||
// InvalidUpdateStrategyReason represents an invalid image update strategy
|
||||
// configuration.
|
||||
InvalidUpdateStrategyReason string = "InvalidUpdateStrategy"
|
||||
|
||||
// InvalidSourceConfigReason represents an invalid source configuration.
|
||||
InvalidSourceConfigReason string = "InvalidSourceConfiguration"
|
||||
|
||||
// SourceManagerFailedReason represents a failure in the SourceManager which
|
||||
// manages the source.
|
||||
SourceManagerFailedReason string = "SourceManagerFailed"
|
||||
|
||||
// GitOperationFailedReason represents a failure in Git source operation.
|
||||
GitOperationFailedReason string = "GitOperationFailed"
|
||||
|
||||
// UpdateFailedReason represents a failure during source update.
|
||||
UpdateFailedReason string = "UpdateFailed"
|
||||
|
||||
// InvalidPolicySelectorReason represents an invalid policy selector.
|
||||
InvalidPolicySelectorReason string = "InvalidPolicySelector"
|
||||
|
||||
// RemovedTemplateFieldReason represents usage of removed template field.
|
||||
RemovedTemplateFieldReason string = "RemovedTemplateField"
|
||||
)
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
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 v1beta2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CrossNamespaceSourceReference contains enough information to let you locate the
|
||||
// typed Kubernetes resource object at cluster level.
|
||||
type CrossNamespaceSourceReference struct {
|
||||
// API version of the referent.
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// Kind of the referent.
|
||||
// +kubebuilder:validation:Enum=GitRepository
|
||||
// +kubebuilder:default=GitRepository
|
||||
// +required
|
||||
Kind string `json:"kind"`
|
||||
|
||||
// Name of the referent.
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
|
||||
// Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
func (s *CrossNamespaceSourceReference) String() string {
|
||||
if s.Namespace != "" {
|
||||
return fmt.Sprintf("%s/%s/%s", s.Kind, s.Namespace, s.Name)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", s.Kind, s.Name)
|
||||
}
|
||||
|
||||
// ImageRef represents an image reference.
|
||||
type ImageRef struct {
|
||||
// Name is the bare image's name.
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
// Tag is the image's tag.
|
||||
// +required
|
||||
Tag string `json:"tag"`
|
||||
// Digest is the image's digest.
|
||||
// +optional
|
||||
Digest string `json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
func (in *ImageRef) String() string {
|
||||
res := in.Name + ":" + in.Tag
|
||||
if in.Digest != "" {
|
||||
res += "@" + in.Digest
|
||||
}
|
||||
return res
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.19.0
|
||||
controller-gen.kubebuilder.io/version: v0.5.0
|
||||
creationTimestamp: null
|
||||
name: imageupdateautomations.image.toolkit.fluxcd.io
|
||||
spec:
|
||||
group: image.toolkit.fluxcd.io
|
||||
|
@ -11,283 +13,99 @@ spec:
|
|||
kind: ImageUpdateAutomation
|
||||
listKind: ImageUpdateAutomationList
|
||||
plural: imageupdateautomations
|
||||
shortNames:
|
||||
- iua
|
||||
- imgupd
|
||||
- imgauto
|
||||
singular: imageupdateautomation
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.conditions[?(@.type=="Ready")].status
|
||||
name: Ready
|
||||
type: string
|
||||
- jsonPath: .status.conditions[?(@.type=="Ready")].message
|
||||
name: Status
|
||||
type: string
|
||||
- jsonPath: .status.lastAutomationRunTime
|
||||
name: Last run
|
||||
priority: 1
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1beta2
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ImageUpdateAutomation is the Schema for the imageupdateautomations
|
||||
API
|
||||
description: ImageUpdateAutomation is the Schema for the imageupdateautomations 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
|
||||
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
|
||||
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: ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation
|
||||
properties:
|
||||
git:
|
||||
description: |-
|
||||
GitSpec contains all the git-specific definitions. This is
|
||||
technically optional, but in practice mandatory until there are
|
||||
other kinds of source allowed.
|
||||
checkout:
|
||||
description: Checkout gives the parameters for cloning the git repository, ready to make changes.
|
||||
properties:
|
||||
checkout:
|
||||
description: |-
|
||||
Checkout gives the parameters for cloning the git repository,
|
||||
ready to make changes. If not present, the `spec.ref` field from the
|
||||
referenced `GitRepository` or its default will be used.
|
||||
branch:
|
||||
description: Branch gives the branch to clone from the git repository. If `.spec.push` is not supplied, commits will also be pushed to this branch.
|
||||
type: string
|
||||
gitRepositoryRef:
|
||||
description: GitRepositoryRef refers to the resource giving access details to a git repository to update files in.
|
||||
properties:
|
||||
ref:
|
||||
description: |-
|
||||
Reference gives a branch, tag or commit to clone from the Git
|
||||
repository.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
name:
|
||||
description: Name of the referent
|
||||
type: string
|
||||
required:
|
||||
- ref
|
||||
type: object
|
||||
commit:
|
||||
description: Commit specifies how to commit to the git repository.
|
||||
properties:
|
||||
author:
|
||||
description: |-
|
||||
Author gives the email and optionally the name to use as the
|
||||
author of commits.
|
||||
properties:
|
||||
email:
|
||||
description: Email gives the email to provide when making
|
||||
a commit.
|
||||
type: string
|
||||
name:
|
||||
description: Name gives the name to provide when making
|
||||
a commit.
|
||||
type: string
|
||||
required:
|
||||
- email
|
||||
type: object
|
||||
messageTemplate:
|
||||
description: |-
|
||||
MessageTemplate provides a template for the commit message,
|
||||
into which will be interpolated the details of the change made.
|
||||
Note: The `Updated` template field has been removed. Use `Changed` instead.
|
||||
type: string
|
||||
messageTemplateValues:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
MessageTemplateValues provides additional values to be available to the
|
||||
templating rendering.
|
||||
type: object
|
||||
signingKey:
|
||||
description: SigningKey provides the option to sign commits
|
||||
with a GPG key
|
||||
properties:
|
||||
secretRef:
|
||||
description: |-
|
||||
SecretRef holds the name to a secret that contains a 'git.asc' key
|
||||
corresponding to the ASCII Armored file containing the GPG signing
|
||||
keypair as the value. It must be in the same namespace as the
|
||||
ImageUpdateAutomation.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- secretRef
|
||||
type: object
|
||||
required:
|
||||
- author
|
||||
type: object
|
||||
push:
|
||||
description: |-
|
||||
Push specifies how and where to push commits made by the
|
||||
automation. If missing, commits are pushed (back) to
|
||||
`.spec.checkout.branch` or its default.
|
||||
properties:
|
||||
branch:
|
||||
description: |-
|
||||
Branch specifies that commits should be pushed to the branch
|
||||
named. The branch is created using `.spec.checkout.branch` as the
|
||||
starting point, if it doesn't already exist.
|
||||
type: string
|
||||
options:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
Options specifies the push options that are sent to the Git
|
||||
server when performing a push operation. For details, see:
|
||||
https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt
|
||||
type: object
|
||||
refspec:
|
||||
description: |-
|
||||
Refspec specifies the Git Refspec to use for a push operation.
|
||||
If both Branch and Refspec are provided, then the commit is pushed
|
||||
to the branch and also using the specified refspec.
|
||||
For more details about Git Refspecs, see:
|
||||
https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
|
||||
type: string
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- commit
|
||||
- branch
|
||||
- gitRepositoryRef
|
||||
type: object
|
||||
commit:
|
||||
description: Commit specifies how to commit to the git repository.
|
||||
properties:
|
||||
authorEmail:
|
||||
description: AuthorEmail gives the email to provide when making a commit
|
||||
type: string
|
||||
authorName:
|
||||
description: AuthorName gives the name to provide when making a commit
|
||||
type: string
|
||||
messageTemplate:
|
||||
description: MessageTemplate provides a template for the commit message, into which will be interpolated the details of the change made.
|
||||
type: string
|
||||
signingKey:
|
||||
description: SigningKey provides the option to sign commits with a GPG key
|
||||
properties:
|
||||
secretRef:
|
||||
description: SecretRef holds the name to a secret that contains a 'git.asc' key corresponding to the ASCII Armored file containing the GPG signing keypair as the value. It must be in the same namespace as the ImageUpdateAutomation.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referent
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- authorEmail
|
||||
- authorName
|
||||
type: object
|
||||
interval:
|
||||
description: |-
|
||||
Interval gives an lower bound for how often the automation
|
||||
run should be attempted.
|
||||
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
|
||||
description: Interval gives an lower bound for how often the automation run should be attempted.
|
||||
type: string
|
||||
policySelector:
|
||||
description: |-
|
||||
PolicySelector allows to filter applied policies based on labels.
|
||||
By default includes all policies in namespace.
|
||||
push:
|
||||
description: Push specifies how and where to push commits made by the automation. If missing, commits are pushed (back) to `.spec.checkout.branch`.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements.
|
||||
The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies
|
||||
to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
sourceRef:
|
||||
description: |-
|
||||
SourceRef refers to the resource giving access details
|
||||
to a git repository.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
kind:
|
||||
default: GitRepository
|
||||
description: Kind of the referent.
|
||||
enum:
|
||||
- GitRepository
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referent.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the referent, defaults to the namespace
|
||||
of the Kubernetes resource object that contains the reference.
|
||||
branch:
|
||||
description: Branch specifies that commits should be pushed to the branch named. The branch is created using `.spec.checkout.branch` as the starting point, if it doesn't already exist.
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
- branch
|
||||
type: object
|
||||
suspend:
|
||||
description: |-
|
||||
Suspend tells the controller to not run this automation, until
|
||||
it is unset (or set to false). Defaults to false.
|
||||
description: Suspend tells the controller to not run this automation, until it is unset (or set to false). Defaults to false.
|
||||
type: boolean
|
||||
update:
|
||||
default:
|
||||
strategy: Setters
|
||||
description: |-
|
||||
Update gives the specification for how to update the files in
|
||||
the repository. This can be left empty, to use the default
|
||||
value.
|
||||
description: Update gives the specification for how to update the files in the repository. This can be left empty, to use the default value.
|
||||
properties:
|
||||
path:
|
||||
description: |-
|
||||
Path to the directory containing the manifests to be updated.
|
||||
Defaults to 'None', which translates to the root path
|
||||
of the GitRepositoryRef.
|
||||
description: Path to the directory containing the manifests to be updated. Defaults to 'None', which translates to the root path of the GitRepositoryRef.
|
||||
type: string
|
||||
strategy:
|
||||
default: Setters
|
||||
|
@ -295,49 +113,36 @@ spec:
|
|||
enum:
|
||||
- Setters
|
||||
type: string
|
||||
required:
|
||||
- strategy
|
||||
type: object
|
||||
required:
|
||||
- checkout
|
||||
- commit
|
||||
- interval
|
||||
- sourceRef
|
||||
type: object
|
||||
status:
|
||||
default:
|
||||
observedGeneration: -1
|
||||
description: ImageUpdateAutomationStatus defines the observed state of
|
||||
ImageUpdateAutomation
|
||||
description: ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
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, 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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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_])?$
|
||||
|
@ -350,7 +155,7 @@ spec:
|
|||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
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
|
||||
|
@ -363,22 +168,14 @@ spec:
|
|||
type: object
|
||||
type: array
|
||||
lastAutomationRunTime:
|
||||
description: |-
|
||||
LastAutomationRunTime records the last time the controller ran
|
||||
this automation through to completion (even if no updates were
|
||||
made).
|
||||
description: LastAutomationRunTime records the last time the controller ran this automation through to completion (even if no updates were made).
|
||||
format: date-time
|
||||
type: string
|
||||
lastHandledReconcileAt:
|
||||
description: |-
|
||||
LastHandledReconcileAt holds the value of the most recent
|
||||
reconcile request value, so a change of the annotation value
|
||||
can be detected.
|
||||
description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected.
|
||||
type: string
|
||||
lastPushCommit:
|
||||
description: |-
|
||||
LastPushCommit records the SHA1 of the last commit made by the
|
||||
controller, for this automation object
|
||||
description: LastPushCommit records the SHA1 of the last commit made by the controller, for this automation object
|
||||
type: string
|
||||
lastPushTime:
|
||||
description: LastPushTime records the time of the last pushed change.
|
||||
|
@ -387,36 +184,429 @@ spec:
|
|||
observedGeneration:
|
||||
format: int64
|
||||
type: integer
|
||||
observedPolicies:
|
||||
additionalProperties:
|
||||
description: ImageRef represents an image reference.
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: false
|
||||
subresources:
|
||||
status: {}
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.lastAutomationRunTime
|
||||
name: Last run
|
||||
type: string
|
||||
name: v1alpha2
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ImageUpdateAutomation is the Schema for the imageupdateautomations 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: ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation
|
||||
properties:
|
||||
git:
|
||||
description: GitSpec contains all the git-specific definitions. This is technically optional, but in practice mandatory until there are other kinds of source allowed.
|
||||
properties:
|
||||
checkout:
|
||||
description: Checkout gives the parameters for cloning the git repository, ready to make changes. If not present, the `spec.ref` field from the referenced `GitRepository` or its default will be used.
|
||||
properties:
|
||||
ref:
|
||||
description: Reference gives a branch, tag or commit to clone from the Git repository.
|
||||
properties:
|
||||
branch:
|
||||
default: master
|
||||
description: The Git branch to checkout, defaults to master.
|
||||
type: string
|
||||
commit:
|
||||
description: The Git commit SHA to checkout, if specified Tag filters will be ignored.
|
||||
type: string
|
||||
semver:
|
||||
description: The Git tag semver expression, takes precedence over Tag.
|
||||
type: string
|
||||
tag:
|
||||
description: The Git tag to checkout, takes precedence over Branch.
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- ref
|
||||
type: object
|
||||
commit:
|
||||
description: Commit specifies how to commit to the git repository.
|
||||
properties:
|
||||
author:
|
||||
description: Author gives the email and optionally the name to use as the author of commits.
|
||||
properties:
|
||||
email:
|
||||
description: Email gives the email to provide when making a commit.
|
||||
type: string
|
||||
name:
|
||||
description: Name gives the name to provide when making a commit.
|
||||
type: string
|
||||
required:
|
||||
- email
|
||||
type: object
|
||||
messageTemplate:
|
||||
description: MessageTemplate provides a template for the commit message, into which will be interpolated the details of the change made.
|
||||
type: string
|
||||
signingKey:
|
||||
description: SigningKey provides the option to sign commits with a GPG key
|
||||
properties:
|
||||
secretRef:
|
||||
description: SecretRef holds the name to a secret that contains a 'git.asc' key corresponding to the ASCII Armored file containing the GPG signing keypair as the value. It must be in the same namespace as the ImageUpdateAutomation.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referent
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- author
|
||||
type: object
|
||||
push:
|
||||
description: Push specifies how and where to push commits made by the automation. If missing, commits are pushed (back) to `.spec.checkout.branch` or its default.
|
||||
properties:
|
||||
branch:
|
||||
description: Branch specifies that commits should be pushed to the branch named. The branch is created using `.spec.checkout.branch` as the starting point, if it doesn't already exist.
|
||||
type: string
|
||||
required:
|
||||
- branch
|
||||
type: object
|
||||
required:
|
||||
- commit
|
||||
type: object
|
||||
interval:
|
||||
description: Interval gives an lower bound for how often the automation run should be attempted.
|
||||
type: string
|
||||
sourceRef:
|
||||
description: SourceRef refers to the resource giving access details to a git repository.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent
|
||||
type: string
|
||||
kind:
|
||||
default: GitRepository
|
||||
description: Kind of the referent
|
||||
enum:
|
||||
- GitRepository
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referent
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
suspend:
|
||||
description: Suspend tells the controller to not run this automation, until it is unset (or set to false). Defaults to false.
|
||||
type: boolean
|
||||
update:
|
||||
default:
|
||||
strategy: Setters
|
||||
description: Update gives the specification for how to update the files in the repository. This can be left empty, to use the default value.
|
||||
properties:
|
||||
path:
|
||||
description: Path to the directory containing the manifests to be updated. Defaults to 'None', which translates to the root path of the GitRepositoryRef.
|
||||
type: string
|
||||
strategy:
|
||||
default: Setters
|
||||
description: Strategy names the strategy to be used.
|
||||
enum:
|
||||
- Setters
|
||||
type: string
|
||||
required:
|
||||
- strategy
|
||||
type: object
|
||||
required:
|
||||
- interval
|
||||
- sourceRef
|
||||
type: object
|
||||
status:
|
||||
description: ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation
|
||||
properties:
|
||||
conditions:
|
||||
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, 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:
|
||||
digest:
|
||||
description: Digest is the image's digest.
|
||||
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
|
||||
name:
|
||||
description: Name is the bare image's name.
|
||||
message:
|
||||
description: message is a human readable message indicating details about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
tag:
|
||||
description: Tag is the image's tag.
|
||||
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:
|
||||
- name
|
||||
- tag
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
description: |-
|
||||
ObservedPolicies is the list of observed ImagePolicies that were
|
||||
considered by the ImageUpdateAutomation update process.
|
||||
type: object
|
||||
observedSourceRevision:
|
||||
description: |-
|
||||
ObservedPolicies []ObservedPolicy `json:"observedPolicies,omitempty"`
|
||||
ObservedSourceRevision is the last observed source revision. This can be
|
||||
used to determine if the source has been updated since last observation.
|
||||
type: array
|
||||
lastAutomationRunTime:
|
||||
description: LastAutomationRunTime records the last time the controller ran this automation through to completion (even if no updates were made).
|
||||
format: date-time
|
||||
type: string
|
||||
lastHandledReconcileAt:
|
||||
description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected.
|
||||
type: string
|
||||
lastPushCommit:
|
||||
description: LastPushCommit records the SHA1 of the last commit made by the controller, for this automation object
|
||||
type: string
|
||||
lastPushTime:
|
||||
description: LastPushTime records the time of the last pushed change.
|
||||
format: date-time
|
||||
type: string
|
||||
observedGeneration:
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: false
|
||||
subresources:
|
||||
status: {}
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.lastAutomationRunTime
|
||||
name: Last run
|
||||
type: string
|
||||
name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ImageUpdateAutomation is the Schema for the imageupdateautomations 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: ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation
|
||||
properties:
|
||||
git:
|
||||
description: GitSpec contains all the git-specific definitions. This is technically optional, but in practice mandatory until there are other kinds of source allowed.
|
||||
properties:
|
||||
checkout:
|
||||
description: Checkout gives the parameters for cloning the git repository, ready to make changes. If not present, the `spec.ref` field from the referenced `GitRepository` or its default will be used.
|
||||
properties:
|
||||
ref:
|
||||
description: Reference gives a branch, tag or commit to clone from the Git repository.
|
||||
properties:
|
||||
branch:
|
||||
default: master
|
||||
description: The Git branch to checkout, defaults to master.
|
||||
type: string
|
||||
commit:
|
||||
description: The Git commit SHA to checkout, if specified Tag filters will be ignored.
|
||||
type: string
|
||||
semver:
|
||||
description: The Git tag semver expression, takes precedence over Tag.
|
||||
type: string
|
||||
tag:
|
||||
description: The Git tag to checkout, takes precedence over Branch.
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- ref
|
||||
type: object
|
||||
commit:
|
||||
description: Commit specifies how to commit to the git repository.
|
||||
properties:
|
||||
author:
|
||||
description: Author gives the email and optionally the name to use as the author of commits.
|
||||
properties:
|
||||
email:
|
||||
description: Email gives the email to provide when making a commit.
|
||||
type: string
|
||||
name:
|
||||
description: Name gives the name to provide when making a commit.
|
||||
type: string
|
||||
required:
|
||||
- email
|
||||
type: object
|
||||
messageTemplate:
|
||||
description: MessageTemplate provides a template for the commit message, into which will be interpolated the details of the change made.
|
||||
type: string
|
||||
signingKey:
|
||||
description: SigningKey provides the option to sign commits with a GPG key
|
||||
properties:
|
||||
secretRef:
|
||||
description: SecretRef holds the name to a secret that contains a 'git.asc' key corresponding to the ASCII Armored file containing the GPG signing keypair as the value. It must be in the same namespace as the ImageUpdateAutomation.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referent
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- author
|
||||
type: object
|
||||
push:
|
||||
description: Push specifies how and where to push commits made by the automation. If missing, commits are pushed (back) to `.spec.checkout.branch` or its default.
|
||||
properties:
|
||||
branch:
|
||||
description: Branch specifies that commits should be pushed to the branch named. The branch is created using `.spec.checkout.branch` as the starting point, if it doesn't already exist.
|
||||
type: string
|
||||
required:
|
||||
- branch
|
||||
type: object
|
||||
required:
|
||||
- commit
|
||||
type: object
|
||||
interval:
|
||||
description: Interval gives an lower bound for how often the automation run should be attempted.
|
||||
type: string
|
||||
sourceRef:
|
||||
description: SourceRef refers to the resource giving access details to a git repository.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent
|
||||
type: string
|
||||
kind:
|
||||
default: GitRepository
|
||||
description: Kind of the referent
|
||||
enum:
|
||||
- GitRepository
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referent
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
suspend:
|
||||
description: Suspend tells the controller to not run this automation, until it is unset (or set to false). Defaults to false.
|
||||
type: boolean
|
||||
update:
|
||||
default:
|
||||
strategy: Setters
|
||||
description: Update gives the specification for how to update the files in the repository. This can be left empty, to use the default value.
|
||||
properties:
|
||||
path:
|
||||
description: Path to the directory containing the manifests to be updated. Defaults to 'None', which translates to the root path of the GitRepositoryRef.
|
||||
type: string
|
||||
strategy:
|
||||
default: Setters
|
||||
description: Strategy names the strategy to be used.
|
||||
enum:
|
||||
- Setters
|
||||
type: string
|
||||
required:
|
||||
- strategy
|
||||
type: object
|
||||
required:
|
||||
- interval
|
||||
- sourceRef
|
||||
type: object
|
||||
status:
|
||||
description: ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation
|
||||
properties:
|
||||
conditions:
|
||||
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, 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
|
||||
lastAutomationRunTime:
|
||||
description: LastAutomationRunTime records the last time the controller ran this automation through to completion (even if no updates were made).
|
||||
format: date-time
|
||||
type: string
|
||||
lastHandledReconcileAt:
|
||||
description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected.
|
||||
type: string
|
||||
lastPushCommit:
|
||||
description: LastPushCommit records the SHA1 of the last commit made by the controller, for this automation object
|
||||
type: string
|
||||
lastPushTime:
|
||||
description: LastPushTime records the time of the last pushed change.
|
||||
format: date-time
|
||||
type: string
|
||||
observedGeneration:
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
|
|
|
@ -29,15 +29,9 @@ spec:
|
|||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop: [ "ALL" ]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http-prom
|
||||
protocol: TCP
|
||||
- containerPort: 9440
|
||||
name: healthz
|
||||
protocol: TCP
|
||||
|
|
|
@ -5,4 +5,4 @@ resources:
|
|||
images:
|
||||
- name: fluxcd/image-automation-controller
|
||||
newName: fluxcd/image-automation-controller
|
||||
newTag: v0.41.2
|
||||
newTag: v0.14.1
|
||||
|
|
|
@ -1,38 +1,11 @@
|
|||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: manager-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- image.toolkit.fluxcd.io
|
||||
resources:
|
||||
- imagepolicies
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- image.toolkit.fluxcd.io
|
||||
resources:
|
||||
- imagepolicies/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- image.toolkit.fluxcd.io
|
||||
resources:
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: image.toolkit.fluxcd.io/v1alpha1
|
||||
kind: ImageUpdateAutomation
|
||||
metadata:
|
||||
name: imageupdateautomation-sample
|
||||
spec:
|
||||
checkout:
|
||||
gitRepositoryRef:
|
||||
name: app-repo
|
||||
branch: main
|
||||
interval: 5m
|
||||
# update strategy is left to default to "Setters"
|
||||
commit:
|
||||
authorName: Fluxbot
|
||||
authorEmail: fluxbot@example.com
|
|
@ -1,29 +0,0 @@
|
|||
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||
kind: ImageUpdateAutomation
|
||||
metadata:
|
||||
name: imageupdateautomation-sample
|
||||
spec:
|
||||
interval: 5m
|
||||
sourceRef:
|
||||
kind: GitRepository # the only valid value, but good practice to be explicit here
|
||||
name: sample-repo
|
||||
git:
|
||||
checkout:
|
||||
ref:
|
||||
branch: main
|
||||
commit:
|
||||
author:
|
||||
name: fluxbot
|
||||
email: fluxbot@example.com
|
||||
messageTemplate: |
|
||||
An automated update from FluxBot
|
||||
|
||||
[ci skip]
|
||||
signingKey:
|
||||
secretRef:
|
||||
name: git-pgp
|
||||
push:
|
||||
branch: auto
|
||||
update:
|
||||
strategy: Setters
|
||||
path: ./cluster/sample
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
Copyright 2020, 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 controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLibgit2ErrorTidy(t *testing.T) {
|
||||
// this is what GitLab sends if the deploy key doesn't have write access
|
||||
gitlabMessage := `remote:
|
||||
remote: ========================================================================
|
||||
remote:
|
||||
remote: This deploy key does not have write access to this project.
|
||||
remote:
|
||||
remote: ========================================================================
|
||||
remote:
|
||||
`
|
||||
expectedReformat := "remote: This deploy key does not have write access to this project."
|
||||
|
||||
err := errors.New(gitlabMessage)
|
||||
err = libgit2PushError(err)
|
||||
reformattedMessage := err.Error()
|
||||
if reformattedMessage != expectedReformat {
|
||||
t.Errorf("expected %q, got %q", expectedReformat, reformattedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibgit2Multiline(t *testing.T) {
|
||||
// this is a hypothetical error message, in which the useful
|
||||
// content spans more than one line
|
||||
multilineMessage := `remote:
|
||||
remote: ========================================================================
|
||||
remote:
|
||||
remote: This deploy key does not have write access to this project.
|
||||
remote: You will need to create a new deploy key.
|
||||
remote:
|
||||
remote: ========================================================================
|
||||
remote:
|
||||
`
|
||||
expectedReformat := "remote: This deploy key does not have write access to this project. You will need to create a new deploy key."
|
||||
|
||||
err := errors.New(multilineMessage)
|
||||
err = libgit2PushError(err)
|
||||
reformattedMessage := err.Error()
|
||||
if reformattedMessage != expectedReformat {
|
||||
t.Errorf("expected %q, got %q", expectedReformat, reformattedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibgit2ErrorUnchanged(t *testing.T) {
|
||||
// this is (roughly) what GitHub sends if the deploy key doesn't have write access
|
||||
regularMessage := `remote: ERROR: deploy key does not have permissions`
|
||||
expectedReformat := regularMessage
|
||||
err := errors.New(regularMessage)
|
||||
err = libgit2PushError(err)
|
||||
reformattedMessage := err.Error()
|
||||
if reformattedMessage != expectedReformat {
|
||||
t.Errorf("expected %q, got %q", expectedReformat, reformattedMessage)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
gogit "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/go-git/go-git/v5/storage/memory"
|
||||
"github.com/go-logr/logr"
|
||||
|
||||
"github.com/fluxcd/pkg/gittestserver"
|
||||
"github.com/fluxcd/source-controller/pkg/git"
|
||||
)
|
||||
|
||||
func populateRepoFromFixture(repo *gogit.Repository, fixture string) error {
|
||||
working, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs := working.Filesystem
|
||||
|
||||
if err = filepath.Walk(fixture, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fs.MkdirAll(fs.Join(path[len(fixture):]), info.Mode())
|
||||
}
|
||||
// copy symlinks as-is, so I can test what happens with broken symlinks
|
||||
if info.Mode()&os.ModeSymlink > 0 {
|
||||
target, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.Symlink(target, path[len(fixture):])
|
||||
}
|
||||
|
||||
fileBytes, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ff, err := fs.Create(path[len(fixture):])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ff.Close()
|
||||
|
||||
_, err = ff.Write(fileBytes)
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = working.Add(".")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = working.Commit("Initial revision from "+fixture, &gogit.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: "Testbot",
|
||||
Email: "test@example.com",
|
||||
When: time.Now(),
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRepoForFixture(t *testing.T) {
|
||||
repo, err := gogit.Init(memory.NewStorage(), memfs.New())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = populateRepoFromFixture(repo, "testdata/pathconfig")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreBrokenSymlink(t *testing.T) {
|
||||
// init a git repo in the filesystem so we can operate on files there
|
||||
tmp, err := ioutil.TempDir("", "flux-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
repo, err := gogit.PlainInit(tmp, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = populateRepoFromFixture(repo, "testdata/brokenlink")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = commitChangedManifests(logr.Discard(), repo, tmp, nil, nil, "unused")
|
||||
if err != errNoChanges {
|
||||
t.Fatalf("expected no changes but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// this is a hook script that will reject a ref update for a branch
|
||||
// that's not `main`
|
||||
const rejectBranch = `
|
||||
if [ "$1" != "refs/heads/main" ]; then
|
||||
echo "*** Rejecting push to non-main branch $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
`
|
||||
|
||||
func TestPushRejected(t *testing.T) {
|
||||
// Check that pushing to a repository which rejects a ref update
|
||||
// results in an error. Why would a repo reject an update? If yu
|
||||
// use e.g., branch protection in GitHub, this is what happens --
|
||||
// see
|
||||
// https://github.com/fluxcd/image-automation-controller/issues/194.
|
||||
|
||||
branch := "push-branch"
|
||||
|
||||
gitServer, err := gittestserver.NewTempGitServer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gitServer.AutoCreate()
|
||||
gitServer.InstallUpdateHook(rejectBranch)
|
||||
|
||||
if err = gitServer.StartHTTP(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// this is currently defined in update_test.go, but handy right here ..
|
||||
if err = initGitRepo(gitServer, "testdata/appconfig", "main", "/appconfig.git"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tmp, err := ioutil.TempDir("", "gotest-imageauto-git")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repoURL := gitServer.HTTPAddress() + "/appconfig.git"
|
||||
repo, err := gogit.PlainClone(tmp, false, &gogit.CloneOptions{
|
||||
URL: repoURL,
|
||||
ReferenceName: plumbing.NewBranchReferenceName("main"),
|
||||
})
|
||||
|
||||
// This is here to guard against push in general being broken
|
||||
err = push(context.TODO(), tmp, "main", repoAccess{
|
||||
url: repoURL,
|
||||
auth: &git.Auth{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This is not under test, but needed for the next bit
|
||||
if err = switchBranch(repo, branch); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This is supposed to fail, because the hook rejects the branch
|
||||
// pushed to.
|
||||
err = push(context.TODO(), tmp, branch, repoAccess{
|
||||
url: repoURL,
|
||||
auth: &git.Auth{},
|
||||
})
|
||||
if err == nil {
|
||||
t.Error("push to a forbidden branch is expected to fail, but succeeded")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,795 @@
|
|||
/*
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
libgit2 "github.com/libgit2/git2go/v31"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-logr/logr"
|
||||
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"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
kuberecorder "k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/tools/reference"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/runtime/events"
|
||||
"github.com/fluxcd/pkg/runtime/metrics"
|
||||
"github.com/fluxcd/pkg/runtime/predicates"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
"github.com/fluxcd/source-controller/pkg/git"
|
||||
gitstrat "github.com/fluxcd/source-controller/pkg/git/strategy"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
"github.com/fluxcd/image-automation-controller/pkg/update"
|
||||
)
|
||||
|
||||
// log level for debug output
|
||||
const debug = 1
|
||||
|
||||
// log level for trace output; the logging system
|
||||
// (fluxcd/pkg/runtime/logging) doesn't presently account for levels
|
||||
// more verbose than debug, so lump tracing into
|
||||
// --log-level=debug. However, it's useful as self-documentation to
|
||||
// keep tracing distinct.
|
||||
const trace = 1
|
||||
|
||||
const originRemote = "origin"
|
||||
|
||||
const defaultMessageTemplate = `Update from image update automation`
|
||||
|
||||
const repoRefKey = ".spec.gitRepository"
|
||||
|
||||
const signingSecretKey = "git.asc"
|
||||
|
||||
// TemplateData is the type of the value given to the commit message
|
||||
// template.
|
||||
type TemplateData struct {
|
||||
AutomationObject types.NamespacedName
|
||||
Updated update.Result
|
||||
}
|
||||
|
||||
// ImageUpdateAutomationReconciler reconciles a ImageUpdateAutomation object
|
||||
type ImageUpdateAutomationReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
EventRecorder kuberecorder.EventRecorder
|
||||
ExternalEventRecorder *events.Recorder
|
||||
MetricsRecorder *metrics.Recorder
|
||||
}
|
||||
|
||||
type ImageUpdateAutomationReconcilerOptions struct {
|
||||
MaxConcurrentReconciles int
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imageupdateautomations,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imageupdateautomations/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=gitrepositories,verbs=get;list;watch
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := logr.FromContext(ctx)
|
||||
debuglog := log.V(debug)
|
||||
tracelog := log.V(trace)
|
||||
now := time.Now()
|
||||
var templateValues TemplateData
|
||||
|
||||
var auto imagev1.ImageUpdateAutomation
|
||||
if err := r.Get(ctx, req.NamespacedName, &auto); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
// record suspension metrics
|
||||
defer r.recordSuspension(ctx, auto)
|
||||
|
||||
if auto.Spec.Suspend {
|
||||
log.Info("ImageUpdateAutomation is suspended, skipping automation run")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
templateValues.AutomationObject = req.NamespacedName
|
||||
|
||||
// Record readiness metric when exiting; if there's any points at
|
||||
// which the readiness is updated _without also exiting_, they
|
||||
// should also record the readiness.
|
||||
defer r.recordReadinessMetric(ctx, &auto)
|
||||
// Record reconciliation duration when exiting
|
||||
if r.MetricsRecorder != nil {
|
||||
objRef, err := reference.GetReference(r.Scheme, &auto)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
defer r.MetricsRecorder.RecordDuration(*objRef, now)
|
||||
}
|
||||
|
||||
// whatever else happens, we've now "seen" the reconcile
|
||||
// annotation if it's there
|
||||
if token, ok := meta.ReconcileAnnotationValue(auto.GetAnnotations()); ok {
|
||||
auto.Status.SetLastHandledReconcileRequest(token)
|
||||
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
}
|
||||
|
||||
// failWithError is a helper for bailing on the reconciliation.
|
||||
failWithError := func(err error) (ctrl.Result, error) {
|
||||
r.event(ctx, auto, events.EventSeverityError, err.Error())
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, meta.ReconciliationFailedReason, err.Error())
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
log.Error(err, "failed to reconcile")
|
||||
}
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
|
||||
// get the git repository object so it can be checked out
|
||||
|
||||
// only GitRepository objects are supported for now
|
||||
if kind := auto.Spec.SourceRef.Kind; kind != sourcev1.GitRepositoryKind {
|
||||
return failWithError(fmt.Errorf("source kind %q not supported", kind))
|
||||
}
|
||||
gitSpec := auto.Spec.GitSpec
|
||||
if gitSpec == nil {
|
||||
return failWithError(fmt.Errorf("source kind %s neccessitates field .spec.git", sourcev1.GitRepositoryKind))
|
||||
}
|
||||
|
||||
var origin sourcev1.GitRepository
|
||||
originName := types.NamespacedName{
|
||||
Name: auto.Spec.SourceRef.Name,
|
||||
Namespace: auto.GetNamespace(),
|
||||
}
|
||||
debuglog.Info("fetching git repository", "gitrepository", originName)
|
||||
|
||||
if err := r.Get(ctx, originName, &origin); err != nil {
|
||||
if client.IgnoreNotFound(err) == nil {
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.GitNotAvailableReason, "referenced git repository is missing")
|
||||
log.Error(err, "referenced git repository does not exist")
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
return ctrl.Result{}, nil // and assume we'll hear about it when it arrives
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// validate the git spec and default any values needed later, before proceeding
|
||||
var ref *sourcev1.GitRepositoryRef
|
||||
if gitSpec.Checkout != nil {
|
||||
ref = &gitSpec.Checkout.Reference
|
||||
tracelog.Info("using git repository ref from .spec.git.checkout", "ref", ref)
|
||||
} else if r := origin.Spec.Reference; r != nil {
|
||||
ref = r
|
||||
tracelog.Info("using git repository ref from GitRepository spec", "ref", ref)
|
||||
} // else remain as `nil`, which is an acceptable value for cloneInto, later.
|
||||
|
||||
var pushBranch string
|
||||
if gitSpec.Push != nil {
|
||||
pushBranch = gitSpec.Push.Branch
|
||||
tracelog.Info("using push branch from .spec.push.branch", "branch", pushBranch)
|
||||
} else {
|
||||
// Here's where it gets constrained. If there's no push branch
|
||||
// given, then the checkout ref must include a branch, and
|
||||
// that can be used.
|
||||
if ref.Branch == "" {
|
||||
failWithError(fmt.Errorf("Push branch not given explicitly, and cannot be inferred from .spec.git.checkout.ref or GitRepository .spec.ref"))
|
||||
}
|
||||
pushBranch = ref.Branch
|
||||
tracelog.Info("using push branch from $ref.branch", "branch", pushBranch)
|
||||
}
|
||||
|
||||
tmp, err := ioutil.TempDir("", fmt.Sprintf("%s-%s", originName.Namespace, originName.Name))
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
// FIXME use context with deadline for at least the following ops
|
||||
|
||||
debuglog.Info("attempting to clone git repository", "gitrepository", originName, "ref", ref, "working", tmp)
|
||||
|
||||
access, err := r.getRepoAccess(ctx, &origin)
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
var repo *gogit.Repository
|
||||
if repo, err = cloneInto(ctx, access, ref, tmp); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
// When there's a push spec, the pushed-to branch is where commits
|
||||
// shall be made
|
||||
|
||||
if gitSpec.Push != nil {
|
||||
if err := fetch(ctx, tmp, pushBranch, access); err != nil && err != errRemoteBranchMissing {
|
||||
return failWithError(err)
|
||||
}
|
||||
if err = switchBranch(repo, pushBranch); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
}
|
||||
|
||||
manifestsPath := tmp
|
||||
if auto.Spec.Update.Path != "" {
|
||||
tracelog.Info("adjusting update path according to .spec.update.path", "base", tmp, "spec-path", auto.Spec.Update.Path)
|
||||
if p, err := securejoin.SecureJoin(tmp, auto.Spec.Update.Path); err != nil {
|
||||
return failWithError(err)
|
||||
} else {
|
||||
manifestsPath = p
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case auto.Spec.Update != nil && auto.Spec.Update.Strategy == imagev1.UpdateStrategySetters:
|
||||
// For setters we first want to compile a list of _all_ the
|
||||
// policies in the same namespace (maybe in the future this
|
||||
// could be filtered by the automation object).
|
||||
var policies imagev1_reflect.ImagePolicyList
|
||||
if err := r.List(ctx, &policies, &client.ListOptions{Namespace: req.NamespacedName.Namespace}); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
debuglog.Info("updating with setters according to image policies", "count", len(policies.Items), "manifests-path", manifestsPath)
|
||||
if tracelog.Enabled() {
|
||||
for _, item := range policies.Items {
|
||||
tracelog.Info("found policy", "namespace", item.Namespace, "name", item.Name, "latest-image", item.Status.LatestImage)
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := updateAccordingToSetters(ctx, tracelog, manifestsPath, policies.Items); err != nil {
|
||||
return failWithError(err)
|
||||
} else {
|
||||
templateValues.Updated = result
|
||||
}
|
||||
default:
|
||||
log.Info("no update strategy given in the spec")
|
||||
// no sense rescheduling until this resource changes
|
||||
r.event(ctx, auto, events.EventSeverityInfo, "no known update strategy in spec, failing trivially")
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.NoStrategyReason, "no known update strategy is given for object")
|
||||
return ctrl.Result{}, r.patchStatus(ctx, req, auto.Status)
|
||||
}
|
||||
|
||||
debuglog.Info("ran updates to working dir", "working", tmp)
|
||||
|
||||
var statusMessage string
|
||||
|
||||
var signingEntity *openpgp.Entity
|
||||
if gitSpec.Commit.SigningKey != nil {
|
||||
signingEntity, err = r.getSigningEntity(ctx, auto)
|
||||
}
|
||||
|
||||
// construct the commit message from template and values
|
||||
msgTmpl := gitSpec.Commit.MessageTemplate
|
||||
if msgTmpl == "" {
|
||||
msgTmpl = defaultMessageTemplate
|
||||
}
|
||||
tmpl, err := template.New("commit message").Parse(msgTmpl)
|
||||
if err != nil {
|
||||
return failWithError(fmt.Errorf("unable to create commit message template from spec: %w", err))
|
||||
}
|
||||
messageBuf := &strings.Builder{}
|
||||
if err := tmpl.Execute(messageBuf, templateValues); err != nil {
|
||||
return failWithError(fmt.Errorf("failed to run template from spec: %w", err))
|
||||
}
|
||||
|
||||
// The status message depends on what happens next. Since there's
|
||||
// more than one way to succeed, there's some if..else below, and
|
||||
// early returns only on failure.
|
||||
author := &object.Signature{
|
||||
Name: gitSpec.Commit.Author.Name,
|
||||
Email: gitSpec.Commit.Author.Email,
|
||||
When: time.Now(),
|
||||
}
|
||||
|
||||
if rev, err := commitChangedManifests(tracelog, repo, tmp, signingEntity, author, messageBuf.String()); err != nil {
|
||||
if err == errNoChanges {
|
||||
r.event(ctx, auto, events.EventSeverityInfo, "no updates made")
|
||||
debuglog.Info("no changes made in working directory; no commit")
|
||||
statusMessage = "no updates made"
|
||||
if lastCommit, lastTime := auto.Status.LastPushCommit, auto.Status.LastPushTime; lastCommit != "" {
|
||||
statusMessage = fmt.Sprintf("%s; last commit %s at %s", statusMessage, lastCommit[:7], lastTime.Format(time.RFC3339))
|
||||
}
|
||||
} else {
|
||||
return failWithError(err)
|
||||
}
|
||||
} else {
|
||||
if err := push(ctx, tmp, pushBranch, access); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
r.event(ctx, auto, events.EventSeverityInfo, "committed and pushed change "+rev+" to "+pushBranch)
|
||||
log.Info("pushed commit to origin", "revision", rev, "branch", pushBranch)
|
||||
auto.Status.LastPushCommit = rev
|
||||
auto.Status.LastPushTime = &metav1.Time{Time: now}
|
||||
statusMessage = "committed and pushed " + rev + " to " + pushBranch
|
||||
}
|
||||
|
||||
// Getting to here is a successful run.
|
||||
auto.Status.LastAutomationRunTime = &metav1.Time{Time: now}
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionTrue, meta.ReconciliationSucceededReason, statusMessage)
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
|
||||
// We're either in this method because something changed, or this
|
||||
// object got requeued. Either way, once successful, we don't need
|
||||
// to see the object again until Interval has passed, or something
|
||||
// changes again.
|
||||
|
||||
interval := intervalOrDefault(&auto)
|
||||
return ctrl.Result{RequeueAfter: interval}, nil
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) SetupWithManager(mgr ctrl.Manager, opts ImageUpdateAutomationReconcilerOptions) error {
|
||||
ctx := context.Background()
|
||||
// Index the git repository object that each I-U-A refers to
|
||||
if err := mgr.GetFieldIndexer().IndexField(ctx, &imagev1.ImageUpdateAutomation{}, repoRefKey, func(obj client.Object) []string {
|
||||
updater := obj.(*imagev1.ImageUpdateAutomation)
|
||||
ref := updater.Spec.SourceRef
|
||||
return []string{ref.Name}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&imagev1.ImageUpdateAutomation{}, builder.WithPredicates(
|
||||
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}))).
|
||||
Watches(&source.Kind{Type: &sourcev1.GitRepository{}}, handler.EnqueueRequestsFromMapFunc(r.automationsForGitRepo)).
|
||||
Watches(&source.Kind{Type: &imagev1_reflect.ImagePolicy{}}, handler.EnqueueRequestsFromMapFunc(r.automationsForImagePolicy)).
|
||||
WithOptions(controller.Options{
|
||||
MaxConcurrentReconciles: opts.MaxConcurrentReconciles,
|
||||
}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) patchStatus(ctx context.Context,
|
||||
req ctrl.Request,
|
||||
newStatus imagev1.ImageUpdateAutomationStatus) error {
|
||||
|
||||
var auto imagev1.ImageUpdateAutomation
|
||||
if err := r.Get(ctx, req.NamespacedName, &auto); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
patch := client.MergeFrom(auto.DeepCopy())
|
||||
auto.Status = newStatus
|
||||
|
||||
return r.Status().Patch(ctx, &auto, patch)
|
||||
}
|
||||
|
||||
// intervalOrDefault gives the interval specified, or if missing, the default
|
||||
func intervalOrDefault(auto *imagev1.ImageUpdateAutomation) time.Duration {
|
||||
if auto.Spec.Interval.Duration < time.Second {
|
||||
return time.Second
|
||||
}
|
||||
return auto.Spec.Interval.Duration
|
||||
}
|
||||
|
||||
// durationSinceLastRun calculates how long it's been since the last
|
||||
// time the automation ran (which you can then use to find how long to
|
||||
// wait until the next run).
|
||||
func durationSinceLastRun(auto *imagev1.ImageUpdateAutomation, now time.Time) time.Duration {
|
||||
last := auto.Status.LastAutomationRunTime
|
||||
if last == nil {
|
||||
return time.Duration(math.MaxInt64) // a fairly long time
|
||||
}
|
||||
return now.Sub(last.Time)
|
||||
}
|
||||
|
||||
// automationsForGitRepo fetches all the automations that refer to a
|
||||
// particular source.GitRepository object.
|
||||
func (r *ImageUpdateAutomationReconciler) automationsForGitRepo(obj client.Object) []reconcile.Request {
|
||||
ctx := context.Background()
|
||||
var autoList imagev1.ImageUpdateAutomationList
|
||||
if err := r.List(ctx, &autoList, client.InNamespace(obj.GetNamespace()),
|
||||
client.MatchingFields{repoRefKey: obj.GetName()}); err != nil {
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, len(autoList.Items), len(autoList.Items))
|
||||
for i := range autoList.Items {
|
||||
reqs[i].NamespacedName.Name = autoList.Items[i].GetName()
|
||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
|
||||
// automationsForImagePolicy fetches all the automation objects that
|
||||
// might depend on a image policy object. Since the link is via
|
||||
// markers in the git repo, _any_ automation object in the same
|
||||
// namespace could be affected.
|
||||
func (r *ImageUpdateAutomationReconciler) automationsForImagePolicy(obj client.Object) []reconcile.Request {
|
||||
ctx := context.Background()
|
||||
var autoList imagev1.ImageUpdateAutomationList
|
||||
if err := r.List(ctx, &autoList, client.InNamespace(obj.GetNamespace())); err != nil {
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, len(autoList.Items), len(autoList.Items))
|
||||
for i := range autoList.Items {
|
||||
reqs[i].NamespacedName.Name = autoList.Items[i].GetName()
|
||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
|
||||
// --- git ops
|
||||
|
||||
// Note: libgit2 is always used for network operations; for cloning,
|
||||
// it will do a non-shallow clone, and for anything else, it doesn't
|
||||
// matter what is used.
|
||||
|
||||
type repoAccess struct {
|
||||
auth *git.Auth
|
||||
url string
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) getRepoAccess(ctx context.Context, repository *sourcev1.GitRepository) (repoAccess, error) {
|
||||
var access repoAccess
|
||||
access.auth = &git.Auth{}
|
||||
access.url = repository.Spec.URL
|
||||
|
||||
authStrat, err := gitstrat.AuthSecretStrategyForURL(access.url, git.CheckoutOptions{GitImplementation: sourcev1.LibGit2Implementation})
|
||||
if err != nil {
|
||||
return access, err
|
||||
}
|
||||
|
||||
if repository.Spec.SecretRef != nil && authStrat != nil {
|
||||
|
||||
name := types.NamespacedName{
|
||||
Namespace: repository.GetNamespace(),
|
||||
Name: repository.Spec.SecretRef.Name,
|
||||
}
|
||||
|
||||
var secret corev1.Secret
|
||||
err = r.Client.Get(ctx, name, &secret)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("auth secret error: %w", err)
|
||||
return access, err
|
||||
}
|
||||
|
||||
access.auth, err = authStrat.Method(secret)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("auth error: %w", err)
|
||||
return access, err
|
||||
}
|
||||
}
|
||||
return access, nil
|
||||
}
|
||||
|
||||
func (r repoAccess) remoteCallbacks() libgit2.RemoteCallbacks {
|
||||
return libgit2.RemoteCallbacks{
|
||||
CertificateCheckCallback: r.auth.CertCallback,
|
||||
CredentialsCallback: r.auth.CredCallback,
|
||||
}
|
||||
}
|
||||
|
||||
// cloneInto clones the upstream repository at the `ref` given (which
|
||||
// can be `nil`). It returns a `*gogit.Repository` since that is used
|
||||
// for committing changes.
|
||||
func cloneInto(ctx context.Context, access repoAccess, ref *sourcev1.GitRepositoryRef, path string) (*gogit.Repository, error) {
|
||||
checkoutStrat, err := gitstrat.CheckoutStrategyForRef(ref, git.CheckoutOptions{GitImplementation: sourcev1.LibGit2Implementation})
|
||||
if err == nil {
|
||||
_, _, err = checkoutStrat.Checkout(ctx, path, access.url, access.auth)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gogit.PlainOpen(path)
|
||||
}
|
||||
|
||||
// switchBranch switches the repo from the current branch to the
|
||||
// branch given. If the branch does not exist, it is created using the
|
||||
// head as the starting point.
|
||||
func switchBranch(repo *gogit.Repository, pushBranch string) error {
|
||||
localBranch := plumbing.NewBranchReferenceName(pushBranch)
|
||||
|
||||
// is the branch already present?
|
||||
_, err := repo.Reference(localBranch, true)
|
||||
var create bool
|
||||
switch {
|
||||
case err == plumbing.ErrReferenceNotFound:
|
||||
// make a new branch, starting at HEAD
|
||||
create = true
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
// local branch found, great
|
||||
break
|
||||
}
|
||||
|
||||
tree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tree.Checkout(&gogit.CheckoutOptions{
|
||||
Branch: localBranch,
|
||||
Create: create,
|
||||
})
|
||||
}
|
||||
|
||||
var errNoChanges error = errors.New("no changes made to working directory")
|
||||
|
||||
func commitChangedManifests(tracelog logr.Logger, repo *gogit.Repository, absRepoPath string, ent *openpgp.Entity, author *object.Signature, message string) (string, error) {
|
||||
working, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
status, err := working.Status()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// go-git has [a bug](https://github.com/go-git/go-git/issues/253)
|
||||
// whereby it thinks broken symlinks to absolute paths are
|
||||
// modified. There's no circumstance in which we want to commit a
|
||||
// change to a broken symlink: so, detect and skip those.
|
||||
var changed bool
|
||||
for file, _ := range status {
|
||||
abspath := filepath.Join(absRepoPath, file)
|
||||
info, err := os.Lstat(abspath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("checking if %s is a symlink: %w", file, err)
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink > 0 {
|
||||
// symlinks are OK; broken symlinks are probably a result
|
||||
// of the bug mentioned above, but not of interest in any
|
||||
// case.
|
||||
if _, err := os.Stat(abspath); os.IsNotExist(err) {
|
||||
tracelog.Info("apparently broken symlink found; ignoring", "path", abspath)
|
||||
continue
|
||||
}
|
||||
}
|
||||
tracelog.Info("adding file", "file", file)
|
||||
working.Add(file)
|
||||
changed = true
|
||||
}
|
||||
|
||||
if !changed {
|
||||
return "", errNoChanges
|
||||
}
|
||||
|
||||
var rev plumbing.Hash
|
||||
if rev, err = working.Commit(message, &gogit.CommitOptions{
|
||||
Author: author,
|
||||
SignKey: ent,
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return rev.String(), nil
|
||||
}
|
||||
|
||||
// getSigningEntity retrieves an OpenPGP entity referenced by the
|
||||
// provided imagev1.ImageUpdateAutomation for git commit signing
|
||||
func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context, auto imagev1.ImageUpdateAutomation) (*openpgp.Entity, error) {
|
||||
// get kubernetes secret
|
||||
secretName := types.NamespacedName{
|
||||
Namespace: auto.GetNamespace(),
|
||||
Name: auto.Spec.GitSpec.Commit.SigningKey.SecretRef.Name,
|
||||
}
|
||||
var secret corev1.Secret
|
||||
if err := r.Get(ctx, secretName, &secret); err != nil {
|
||||
return nil, fmt.Errorf("could not find signing key secret '%s': %w", secretName, err)
|
||||
}
|
||||
|
||||
// get data from secret
|
||||
data, ok := secret.Data[signingSecretKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("signing key secret '%s' does not contain a 'git.asc' key", secretName)
|
||||
}
|
||||
|
||||
// read entity from secret value
|
||||
entities, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read signing key from secret '%s': %w", secretName, err)
|
||||
}
|
||||
if len(entities) > 1 {
|
||||
return nil, fmt.Errorf("multiple entities read from secret '%s', could not determine which signing key to use", secretName)
|
||||
}
|
||||
return entities[0], nil
|
||||
}
|
||||
|
||||
var errRemoteBranchMissing = errors.New("remote branch missing")
|
||||
|
||||
// fetch gets the remote branch given and updates the local branch
|
||||
// head of the same name, so it can be switched to. If the fetch
|
||||
// completes, it returns nil; if the remote branch is missing, it
|
||||
// returns errRemoteBranchMissing (this is to work in sympathy with
|
||||
// `switchBranch`, which will create the branch if it doesn't
|
||||
// exist). For any other problem it will return the error.
|
||||
func fetch(ctx context.Context, path string, branch string, access repoAccess) error {
|
||||
refspec := fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)
|
||||
repo, err := libgit2.OpenRepository(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
origin, err := repo.Remotes.Lookup(originRemote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = origin.Fetch(
|
||||
[]string{refspec},
|
||||
&libgit2.FetchOptions{
|
||||
RemoteCallbacks: access.remoteCallbacks(),
|
||||
}, "",
|
||||
)
|
||||
if err != nil && libgit2.IsErrorCode(err, libgit2.ErrorCodeNotFound) {
|
||||
return errRemoteBranchMissing
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// push pushes the branch given to the origin using the git library
|
||||
// indicated by `impl`. It's passed both the path to the repo and a
|
||||
// gogit.Repository value, since the latter may as well be used if the
|
||||
// implementation is GoGit.
|
||||
func push(ctx context.Context, path, branch string, access repoAccess) error {
|
||||
repo, err := libgit2.OpenRepository(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
origin, err := repo.Remotes.Lookup(originRemote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
callbacks := access.remoteCallbacks()
|
||||
|
||||
// calling repo.Push will succeed even if a reference update is
|
||||
// rejected; to detect this case, this callback is supplied.
|
||||
var callbackErr error
|
||||
callbacks.PushUpdateReferenceCallback = func(refname, status string) libgit2.ErrorCode {
|
||||
if status != "" {
|
||||
callbackErr = fmt.Errorf("ref %s rejected: %s", refname, status)
|
||||
}
|
||||
return libgit2.ErrOk
|
||||
}
|
||||
err = origin.Push([]string{fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)}, &libgit2.PushOptions{
|
||||
RemoteCallbacks: callbacks,
|
||||
})
|
||||
if err != nil {
|
||||
return libgit2PushError(err)
|
||||
}
|
||||
return callbackErr
|
||||
}
|
||||
|
||||
func libgit2PushError(err error) error {
|
||||
if err == nil {
|
||||
return err
|
||||
}
|
||||
// libgit2 returns the whole output from stderr, and we only need
|
||||
// the message. GitLab likes to return a banner, so as an
|
||||
// heuristic, strip any lines that are just "remote:" and spaces
|
||||
// or fencing.
|
||||
msg := err.Error()
|
||||
lines := strings.Split(msg, "\n")
|
||||
if len(lines) == 1 {
|
||||
return err
|
||||
}
|
||||
var b strings.Builder
|
||||
// the following removes the prefix "remote:" from each line; to
|
||||
// retain a bit of fidelity to the original error, start with it.
|
||||
b.WriteString("remote: ")
|
||||
|
||||
var appending bool
|
||||
for _, line := range lines {
|
||||
m := strings.TrimPrefix(line, "remote:")
|
||||
if m = strings.Trim(m, " \t="); m != "" {
|
||||
if appending {
|
||||
b.WriteString(" ")
|
||||
}
|
||||
b.WriteString(m)
|
||||
appending = true
|
||||
}
|
||||
}
|
||||
return errors.New(b.String())
|
||||
}
|
||||
|
||||
// --- events, metrics
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) event(ctx context.Context, auto imagev1.ImageUpdateAutomation, severity, msg string) {
|
||||
if r.EventRecorder != nil {
|
||||
r.EventRecorder.Event(&auto, "Normal", severity, msg)
|
||||
}
|
||||
if r.ExternalEventRecorder != nil {
|
||||
objRef, err := reference.GetReference(r.Scheme, &auto)
|
||||
if err != nil {
|
||||
logr.FromContext(ctx).Error(err, "unable to send event")
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ExternalEventRecorder.Eventf(*objRef, nil, severity, severity, msg); err != nil {
|
||||
logr.FromContext(ctx).Error(err, "unable to send event")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) recordReadinessMetric(ctx context.Context, auto *imagev1.ImageUpdateAutomation) {
|
||||
if r.MetricsRecorder == nil {
|
||||
return
|
||||
}
|
||||
|
||||
objRef, err := reference.GetReference(r.Scheme, auto)
|
||||
if err != nil {
|
||||
logr.FromContext(ctx).Error(err, "unable to record readiness metric")
|
||||
return
|
||||
}
|
||||
if rc := apimeta.FindStatusCondition(auto.Status.Conditions, meta.ReadyCondition); rc != nil {
|
||||
r.MetricsRecorder.RecordCondition(*objRef, *rc, !auto.DeletionTimestamp.IsZero())
|
||||
} else {
|
||||
r.MetricsRecorder.RecordCondition(*objRef, metav1.Condition{
|
||||
Type: meta.ReadyCondition,
|
||||
Status: metav1.ConditionUnknown,
|
||||
}, !auto.DeletionTimestamp.IsZero())
|
||||
}
|
||||
}
|
||||
|
||||
// --- updates
|
||||
|
||||
// updateAccordingToSetters updates files under the root by treating
|
||||
// the given image policies as kyaml setters.
|
||||
func updateAccordingToSetters(ctx context.Context, tracelog logr.Logger, path string, policies []imagev1_reflect.ImagePolicy) (update.Result, error) {
|
||||
return update.UpdateWithSetters(tracelog, path, path, policies)
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) recordSuspension(ctx context.Context, auto imagev1.ImageUpdateAutomation) {
|
||||
if r.MetricsRecorder == nil {
|
||||
return
|
||||
}
|
||||
log := logr.FromContext(ctx)
|
||||
|
||||
objRef, err := reference.GetReference(r.Scheme, &auto)
|
||||
if err != nil {
|
||||
log.Error(err, "unable to record suspended metric")
|
||||
return
|
||||
}
|
||||
|
||||
if !auto.DeletionTimestamp.IsZero() {
|
||||
r.MetricsRecorder.RecordSuspend(*objRef, false)
|
||||
} else {
|
||||
r.MetricsRecorder.RecordSuspend(*objRef, auto.Spec.Suspend)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var k8sManager ctrl.Manager
|
||||
var imageAutoReconciler *ImageUpdateAutomationReconciler
|
||||
var testEnv *envtest.Environment
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
"Controller Suite",
|
||||
[]Reporter{printer.NewlineReporter{}})
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
ctrl.SetLogger(
|
||||
zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)),
|
||||
)
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{
|
||||
filepath.Join("..", "config", "crd", "bases"),
|
||||
filepath.Join("testdata", "crds"),
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
Expect(sourcev1.AddToScheme(scheme.Scheme)).To(Succeed())
|
||||
Expect(imagev1_reflect.AddToScheme(scheme.Scheme)).To(Succeed())
|
||||
|
||||
Expect(imagev1.AddToScheme(scheme.Scheme)).To(Succeed())
|
||||
// +kubebuilder:scaffold:scheme
|
||||
|
||||
k8sManager, err = ctrl.NewManager(cfg, ctrl.Options{
|
||||
Scheme: scheme.Scheme,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
imageAutoReconciler = &ImageUpdateAutomationReconciler{
|
||||
Client: k8sManager.GetClient(),
|
||||
Scheme: scheme.Scheme,
|
||||
}
|
||||
Expect(imageAutoReconciler.SetupWithManager(k8sManager, ImageUpdateAutomationReconcilerOptions{})).To(Succeed())
|
||||
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
err = k8sManager.Start(ctrl.SetupSignalHandler())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}()
|
||||
|
||||
// Specifically an uncached client. Use <reconciler>.Get if you
|
||||
// want to see what the reconcilers see.
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
close(done)
|
||||
}, 60)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
|
@ -6,5 +6,5 @@ spec:
|
|||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.1 # SETTER_SITE
|
||||
- name: hello
|
||||
image: helloworld:1.0.1 # SETTER_SITE
|
|
@ -6,5 +6,5 @@ spec:
|
|||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.1 # SETTER_SITE
|
||||
- name: hello
|
||||
image: helloworld:1.2.0 # SETTER_SITE
|
|
@ -6,5 +6,5 @@ spec:
|
|||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
||||
- name: hello
|
||||
image: helloworld:1.0.1 # SETTER_SITE
|
|
@ -0,0 +1 @@
|
|||
/surely/does/not/exist
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
|||
<h1>Image update automation API reference v1beta1</h1>
|
||||
<h1>Image update automation API reference</h1>
|
||||
<p>Packages:</p>
|
||||
<ul class="simple">
|
||||
<li>
|
||||
|
@ -119,74 +119,6 @@ string
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta1.CrossNamespaceSourceReference">CrossNamespaceSourceReference
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta1.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec</a>)
|
||||
</p>
|
||||
<p>CrossNamespaceSourceReference contains enough information to let you locate the
|
||||
typed Kubernetes resource object at cluster level.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>apiVersion</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>API version of the referent.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>kind</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Kind of the referent.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>name</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Name of the referent.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>namespace</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta1.GitCheckoutSpec">GitCheckoutSpec
|
||||
</h3>
|
||||
<p>
|
||||
|
@ -207,8 +139,8 @@ string
|
|||
<td>
|
||||
<code>ref</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/source-controller/api/v1#GitRepositoryRef">
|
||||
Source /v1.GitRepositoryRef
|
||||
<a href="https://godoc.org/github.com/fluxcd/source-controller/api/v1beta1#GitRepositoryRef">
|
||||
Source /v1beta1.GitRepositoryRef
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
|
@ -330,8 +262,8 @@ ImageUpdateAutomationSpec
|
|||
<td>
|
||||
<code>sourceRef</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta1.CrossNamespaceSourceReference">
|
||||
CrossNamespaceSourceReference
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta1.SourceReference">
|
||||
SourceReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
|
@ -360,7 +292,7 @@ other kinds of source allowed.</p>
|
|||
<td>
|
||||
<code>interval</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
<a href="https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
Kubernetes meta/v1.Duration
|
||||
</a>
|
||||
</em>
|
||||
|
@ -438,8 +370,8 @@ ImageUpdateAutomationStatus
|
|||
<td>
|
||||
<code>sourceRef</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta1.CrossNamespaceSourceReference">
|
||||
CrossNamespaceSourceReference
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta1.SourceReference">
|
||||
SourceReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
|
@ -468,7 +400,7 @@ other kinds of source allowed.</p>
|
|||
<td>
|
||||
<code>interval</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
<a href="https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
Kubernetes meta/v1.Duration
|
||||
</a>
|
||||
</em>
|
||||
|
@ -598,7 +530,7 @@ int64
|
|||
<td>
|
||||
<code>ReconcileRequestStatus</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#ReconcileRequestStatus">
|
||||
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#ReconcileRequestStatus">
|
||||
github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
|
||||
</a>
|
||||
</em>
|
||||
|
@ -638,42 +570,11 @@ string
|
|||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Branch specifies that commits should be pushed to the branch
|
||||
named. The branch is created using <code>.spec.checkout.branch</code> as the
|
||||
starting point, if it doesn’t already exist.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>refspec</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Refspec specifies the Git Refspec to use for a push operation.
|
||||
If both Branch and Refspec are provided, then the commit is pushed
|
||||
to the branch and also using the specified refspec.
|
||||
For more details about Git Refspecs, see:
|
||||
<a href="https://git-scm.com/book/en/v2/Git-Internals-The-Refspec">https://git-scm.com/book/en/v2/Git-Internals-The-Refspec</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>options</code><br>
|
||||
<em>
|
||||
map[string]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Options specifies the push options that are sent to the Git
|
||||
server when performing a push operation. For details, see:
|
||||
<a href="https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt">https://git-scm.com/docs/git-push#Documentation/git-push.txt—push-optionltoptiongt</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -699,7 +600,7 @@ server when performing a push operation. For details, see:
|
|||
<td>
|
||||
<code>secretRef</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||
</a>
|
||||
</em>
|
||||
|
@ -715,6 +616,62 @@ ImageUpdateAutomation.</p>
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta1.SourceReference">SourceReference
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta1.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec</a>)
|
||||
</p>
|
||||
<p>SourceReference contains enough information to let you locate the
|
||||
typed, referenced source object.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>apiVersion</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>API version of the referent</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>kind</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Kind of the referent</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>name</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Name of the referent</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta1.UpdateStrategy">UpdateStrategy
|
||||
</h3>
|
||||
<p>
|
|
@ -1,909 +0,0 @@
|
|||
<h1>Image update automation API reference v1beta2</h1>
|
||||
<p>Packages:</p>
|
||||
<ul class="simple">
|
||||
<li>
|
||||
<a href="#image.toolkit.fluxcd.io%2fv1beta2">image.toolkit.fluxcd.io/v1beta2</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="image.toolkit.fluxcd.io/v1beta2">image.toolkit.fluxcd.io/v1beta2</h2>
|
||||
<p>Package v1beta2 contains API types for the image API group, version
|
||||
v1beta2. The types here are concerned with automated updates to
|
||||
git, based on metadata from OCI image registries gathered by the
|
||||
image-reflector-controller.</p>
|
||||
Resource Types:
|
||||
<ul class="simple"></ul>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.CommitSpec">CommitSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">GitSpec</a>)
|
||||
</p>
|
||||
<p>CommitSpec specifies how to commit changes to the git repository</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>author</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CommitUser">
|
||||
CommitUser
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Author gives the email and optionally the name to use as the
|
||||
author of commits.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>signingKey</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.SigningKey">
|
||||
SigningKey
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>SigningKey provides the option to sign commits with a GPG key</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>messageTemplate</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>MessageTemplate provides a template for the commit message,
|
||||
into which will be interpolated the details of the change made.
|
||||
Note: The <code>Updated</code> template field has been removed. Use <code>Changed</code> instead.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>messageTemplateValues</code><br>
|
||||
<em>
|
||||
map[string]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>MessageTemplateValues provides additional values to be available to the
|
||||
templating rendering.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.CommitUser">CommitUser
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CommitSpec">CommitSpec</a>)
|
||||
</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>name</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Name gives the name to provide when making a commit.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>email</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Email gives the email to provide when making a commit.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.CrossNamespaceSourceReference">CrossNamespaceSourceReference
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec</a>)
|
||||
</p>
|
||||
<p>CrossNamespaceSourceReference contains enough information to let you locate the
|
||||
typed Kubernetes resource object at cluster level.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>apiVersion</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>API version of the referent.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>kind</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Kind of the referent.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>name</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Name of the referent.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>namespace</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.GitCheckoutSpec">GitCheckoutSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">GitSpec</a>)
|
||||
</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>ref</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/source-controller/api/v1#GitRepositoryRef">
|
||||
Source /v1.GitRepositoryRef
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Reference gives a branch, tag or commit to clone from the Git
|
||||
repository.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.GitSpec">GitSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec</a>)
|
||||
</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>checkout</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitCheckoutSpec">
|
||||
GitCheckoutSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Checkout gives the parameters for cloning the git repository,
|
||||
ready to make changes. If not present, the <code>spec.ref</code> field from the
|
||||
referenced <code>GitRepository</code> or its default will be used.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>commit</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CommitSpec">
|
||||
CommitSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Commit specifies how to commit to the git repository.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>push</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.PushSpec">
|
||||
PushSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Push specifies how and where to push commits made by the
|
||||
automation. If missing, commits are pushed (back) to
|
||||
<code>.spec.checkout.branch</code> or its default.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ImageRef">ImageRef
|
||||
</h3>
|
||||
<p>ImageRef represents an image reference.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>name</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Name is the bare image’s name.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>tag</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Tag is the image’s tag.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>digest</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Digest is the image’s digest.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomation">ImageUpdateAutomation
|
||||
</h3>
|
||||
<p>ImageUpdateAutomation is the Schema for the imageupdateautomations API</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>metadata</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta">
|
||||
Kubernetes meta/v1.ObjectMeta
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
Refer to the Kubernetes API documentation for the fields of the
|
||||
<code>metadata</code> field.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>spec</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">
|
||||
ImageUpdateAutomationSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<br/>
|
||||
<br/>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<code>sourceRef</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CrossNamespaceSourceReference">
|
||||
CrossNamespaceSourceReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>SourceRef refers to the resource giving access details
|
||||
to a git repository.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>git</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">
|
||||
GitSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>GitSpec contains all the git-specific definitions. This is
|
||||
technically optional, but in practice mandatory until there are
|
||||
other kinds of source allowed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>interval</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
Kubernetes meta/v1.Duration
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Interval gives an lower bound for how often the automation
|
||||
run should be attempted.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>policySelector</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselector-v1-meta">
|
||||
Kubernetes meta/v1.LabelSelector
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>PolicySelector allows to filter applied policies based on labels.
|
||||
By default includes all policies in namespace.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>update</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">
|
||||
UpdateStrategy
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Update gives the specification for how to update the files in
|
||||
the repository. This can be left empty, to use the default
|
||||
value.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>suspend</code><br>
|
||||
<em>
|
||||
bool
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Suspend tells the controller to not run this automation, until
|
||||
it is unset (or set to false). Defaults to false.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>status</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationStatus">
|
||||
ImageUpdateAutomationStatus
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomation">ImageUpdateAutomation</a>)
|
||||
</p>
|
||||
<p>ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>sourceRef</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CrossNamespaceSourceReference">
|
||||
CrossNamespaceSourceReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>SourceRef refers to the resource giving access details
|
||||
to a git repository.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>git</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">
|
||||
GitSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>GitSpec contains all the git-specific definitions. This is
|
||||
technically optional, but in practice mandatory until there are
|
||||
other kinds of source allowed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>interval</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
Kubernetes meta/v1.Duration
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Interval gives an lower bound for how often the automation
|
||||
run should be attempted.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>policySelector</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselector-v1-meta">
|
||||
Kubernetes meta/v1.LabelSelector
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>PolicySelector allows to filter applied policies based on labels.
|
||||
By default includes all policies in namespace.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>update</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">
|
||||
UpdateStrategy
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Update gives the specification for how to update the files in
|
||||
the repository. This can be left empty, to use the default
|
||||
value.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>suspend</code><br>
|
||||
<em>
|
||||
bool
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Suspend tells the controller to not run this automation, until
|
||||
it is unset (or set to false). Defaults to false.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationStatus">ImageUpdateAutomationStatus
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomation">ImageUpdateAutomation</a>)
|
||||
</p>
|
||||
<p>ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>lastAutomationRunTime</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#time-v1-meta">
|
||||
Kubernetes meta/v1.Time
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>LastAutomationRunTime records the last time the controller ran
|
||||
this automation through to completion (even if no updates were
|
||||
made).</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>lastPushCommit</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>LastPushCommit records the SHA1 of the last commit made by the
|
||||
controller, for this automation object</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>lastPushTime</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#time-v1-meta">
|
||||
Kubernetes meta/v1.Time
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>LastPushTime records the time of the last pushed change.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedGeneration</code><br>
|
||||
<em>
|
||||
int64
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>conditions</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#condition-v1-meta">
|
||||
[]Kubernetes meta/v1.Condition
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedPolicies</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ObservedPolicies">
|
||||
ObservedPolicies
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ObservedPolicies is the list of observed ImagePolicies that were
|
||||
considered by the ImageUpdateAutomation update process.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedSourceRevision</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ObservedPolicies []ObservedPolicy <code>json:"observedPolicies,omitempty"</code>
|
||||
ObservedSourceRevision is the last observed source revision. This can be
|
||||
used to determine if the source has been updated since last observation.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>ReconcileRequestStatus</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#ReconcileRequestStatus">
|
||||
github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>
|
||||
(Members of <code>ReconcileRequestStatus</code> are embedded into this type.)
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ObservedPolicies">ObservedPolicies
|
||||
(<code>map[string]./api/v1beta2.ImageRef</code> alias)</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationStatus">ImageUpdateAutomationStatus</a>)
|
||||
</p>
|
||||
<p>ObservedPolicies is a map of policy name and ImageRef of their latest
|
||||
ImageRef.</p>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.PushSpec">PushSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">GitSpec</a>)
|
||||
</p>
|
||||
<p>PushSpec specifies how and where to push commits.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>branch</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Branch specifies that commits should be pushed to the branch
|
||||
named. The branch is created using <code>.spec.checkout.branch</code> as the
|
||||
starting point, if it doesn’t already exist.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>refspec</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Refspec specifies the Git Refspec to use for a push operation.
|
||||
If both Branch and Refspec are provided, then the commit is pushed
|
||||
to the branch and also using the specified refspec.
|
||||
For more details about Git Refspecs, see:
|
||||
<a href="https://git-scm.com/book/en/v2/Git-Internals-The-Refspec">https://git-scm.com/book/en/v2/Git-Internals-The-Refspec</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>options</code><br>
|
||||
<em>
|
||||
map[string]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Options specifies the push options that are sent to the Git
|
||||
server when performing a push operation. For details, see:
|
||||
<a href="https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt">https://git-scm.com/docs/git-push#Documentation/git-push.txt—push-optionltoptiongt</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.SigningKey">SigningKey
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CommitSpec">CommitSpec</a>)
|
||||
</p>
|
||||
<p>SigningKey references a Kubernetes secret that contains a GPG keypair</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>secretRef</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>SecretRef holds the name to a secret that contains a ‘git.asc’ key
|
||||
corresponding to the ASCII Armored file containing the GPG signing
|
||||
keypair as the value. It must be in the same namespace as the
|
||||
ImageUpdateAutomation.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">UpdateStrategy
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec</a>)
|
||||
</p>
|
||||
<p>UpdateStrategy is a union of the various strategies for updating
|
||||
the Git repository. Parameters for each strategy (if any) can be
|
||||
inlined here.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>strategy</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategyName">
|
||||
UpdateStrategyName
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Strategy names the strategy to be used.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>path</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Path to the directory containing the manifests to be updated.
|
||||
Defaults to ‘None’, which translates to the root path
|
||||
of the GitRepositoryRef.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.UpdateStrategyName">UpdateStrategyName
|
||||
(<code>string</code> alias)</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">UpdateStrategy</a>)
|
||||
</p>
|
||||
<p>UpdateStrategyName is the type for names that go in
|
||||
.update.strategy. NB the value in the const immediately below.</p>
|
||||
<div class="admonition note">
|
||||
<p class="last">This page was automatically generated with <code>gen-crd-api-reference-docs</code></p>
|
||||
</div>
|
|
@ -368,8 +368,8 @@ There is one condition maintained by the controller, which is the usual `ReadyCo
|
|||
condition. This will be recorded as `True` when automation has run without errors, whether or not it
|
||||
resulted in a commit.
|
||||
|
||||
[image-auto-guide]: https://fluxcd.io/flux/guides/image-update/#configure-image-update-for-custom-resources
|
||||
[git-repo-ref]: https://fluxcd.io/flux/components/source/gitrepositories/
|
||||
[image-auto-guide]: https://toolkit.fluxcd.io/guides/image-update/#configure-image-update-for-custom-resources
|
||||
[git-repo-ref]: https://toolkit.fluxcd.io/components/source/gitrepositories/
|
||||
[durations]: https://godoc.org/time#ParseDuration
|
||||
[source-docs]: https://fluxcd.io/flux/components/source/gitrepositories/#git-implementation
|
||||
[source-docs]: https://toolkit.fluxcd.io/components/source/gitrepositories/#git-implementation
|
||||
[go-text-template]: https://golang.org/pkg/text/template/
|
||||
|
|
|
@ -591,8 +591,8 @@ spec:
|
|||
branch: auto
|
||||
```
|
||||
|
||||
[image-auto-guide]: https://fluxcd.io/flux/guides/image-update/#configure-image-update-for-custom-resources
|
||||
[git-repo-ref]: https://fluxcd.io/flux/components/source/gitrepositories/#specification
|
||||
[image-auto-guide]: https://toolkit.fluxcd.io/guides/image-update/#configure-image-update-for-custom-resources
|
||||
[git-repo-ref]: https://toolkit.fluxcd.io/components/source/gitrepositories/#specification
|
||||
[durations]: https://godoc.org/time#ParseDuration
|
||||
[source-docs]: https://fluxcd.io/flux/components/source/gitrepositories/#git-implementation
|
||||
[source-docs]: https://toolkit.fluxcd.io/components/source/gitrepositories/#git-implementation
|
||||
[go-text-template]: https://golang.org/pkg/text/template/
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<!-- -*- fill-column: 100 -*- -->
|
||||
# Image Update Automations
|
||||
|
||||
<!-- menuweight:50 -->
|
||||
|
||||
The `ImageUpdateAutomation` type defines an automation process that will update a git repository,
|
||||
based on image policy objects in the same namespace.
|
||||
|
||||
|
@ -34,7 +32,7 @@ type ImageUpdateAutomationSpec struct {
|
|||
// SourceRef refers to the resource giving access details
|
||||
// to a git repository.
|
||||
// +required
|
||||
SourceRef CrossNamespaceSourceReference `json:"sourceRef"`
|
||||
SourceRef SourceReference `json:"sourceRef"`
|
||||
// GitSpec contains all the git-specific definitions. This is
|
||||
// technically optional, but in practice mandatory until there are
|
||||
// other kinds of source allowed.
|
||||
|
@ -64,57 +62,32 @@ repository to be updated. The `kind` field in the reference currently only suppo
|
|||
`GitRepository`, which is the default.
|
||||
|
||||
```go
|
||||
// CrossNamespaceSourceReference contains enough information to let you locate the
|
||||
// typed Kubernetes resource object at cluster level.
|
||||
type CrossNamespaceSourceReference struct {
|
||||
// API version of the referent.
|
||||
// SourceReference contains enough information to let you locate the
|
||||
// typed, referenced source object.
|
||||
type SourceReference struct {
|
||||
// API version of the referent
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// Kind of the referent.
|
||||
// Kind of the referent
|
||||
// +kubebuilder:validation:Enum=GitRepository
|
||||
// +kubebuilder:default=GitRepository
|
||||
// +required
|
||||
Kind string `json:"kind"`
|
||||
|
||||
// Name of the referent.
|
||||
// Name of the referent
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
|
||||
// Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
### Cross-namespace references
|
||||
|
||||
A ImageUpdateAutomation can refer to a GitRepository from a different namespace with
|
||||
`spec.sourceRef.namespace` e.g.:
|
||||
|
||||
```yaml
|
||||
apiVersion: image.toolkit.fluxcd.io/v1beta1
|
||||
kind: ImageUpdateAutomation
|
||||
metadata:
|
||||
name: webapp
|
||||
namespace: apps
|
||||
spec:
|
||||
interval: 5m
|
||||
sourceRef:
|
||||
kind: GitRepository # the only valid value, but good practice to be explicit here
|
||||
name: apps
|
||||
namespace: flux-system
|
||||
```
|
||||
|
||||
On multi-tenant clusters, platform admins can disable cross-namespace references with the
|
||||
`--no-cross-namespace-refs=true` flag.
|
||||
|
||||
To be able to commit changes back, the referenced `GitRepository` object must refer to credentials
|
||||
with write access; e.g., if using a GitHub deploy key, "Allow write access" should be checked when
|
||||
creating it. Only the `url`, `ref`, and `secretRef` fields of the `GitRepository` are used.
|
||||
|
||||
The [`gitImplementation` field][source-docs] in the referenced `GitRepository` is ignored. All
|
||||
reconciliations are executed using the `go-git` implementation.
|
||||
The [`gitImplementation` field][source-docs] in the referenced `GitRepository` is ignored. The
|
||||
automation controller cannot use shallow clones or submodules, so there is no reason to use the
|
||||
go-git implementation rather than libgit2.
|
||||
|
||||
Other fields particular to how the Git repository is used are in the `git` field, [described
|
||||
below](#git-specific-specification).
|
||||
|
@ -170,9 +143,6 @@ When `checkout` is given, it overrides the analogous field in the `GitRepository
|
|||
in `.spec.sourceRef`. You would use this to put automation commits on a different branch than that
|
||||
you are syncing, for example.
|
||||
|
||||
By default the controller will only do shallow clones, but this can be disabled by starting the controller
|
||||
with `--feature-gates=GitShallowClone=false`.
|
||||
|
||||
### Commit
|
||||
|
||||
The `.spec.git.commit` field gives details to use when making a commit to push to the Git repository:
|
||||
|
@ -228,21 +198,7 @@ will result in commits with the author `Fluxbot <flux@example.com>`.
|
|||
|
||||
The optional `signingKey` field can be used to provide a key to sign commits with. It holds a
|
||||
reference to a secret, which is expected to have a file called `git.asc` containing an
|
||||
ASCII-armoured PGP key. If the private key is protected by a password, you can specify the same
|
||||
in the secret using the `passphrase` key.
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: signing-key
|
||||
namespace: default
|
||||
stringData:
|
||||
git.asc: |
|
||||
<ARMOR ENCODED PGP KEY>
|
||||
passphrase: <private-key-passphrase>
|
||||
```
|
||||
ASCII-armoured PGP key.
|
||||
|
||||
The `messageTemplate` field is a string which will be used as a template for the commit message. If
|
||||
empty, there is a default message; but you will likely want to provide your own, especially if you
|
||||
|
@ -266,7 +222,7 @@ The message template is a [Go text template][go-text-template]. The data availab
|
|||
have this structure (not reproduced verbatim):
|
||||
|
||||
```go
|
||||
// internal/controller/imageupdateautomation_controller.go
|
||||
// controllers/imageupdateautomation_controller.go
|
||||
|
||||
// TemplateData is the type of the value given to the commit message
|
||||
// template.
|
||||
|
@ -359,45 +315,6 @@ spec:
|
|||
- {{.}}
|
||||
{{ end -}}
|
||||
```
|
||||
#### Commit Message with Template functions
|
||||
|
||||
With template functions, it is possible to manipulate and transform the supplied data in order to generate more complex commit messages.
|
||||
|
||||
```yaml
|
||||
kind: ImageUpdateAutomation
|
||||
metadata:
|
||||
name: flux-system
|
||||
spec:
|
||||
git:
|
||||
commit:
|
||||
messageTemplate: |
|
||||
Automated image update
|
||||
|
||||
Automation name: {{ .AutomationObject }}
|
||||
|
||||
Files:
|
||||
{{ range $filename, $_ := .Updated.Files -}}
|
||||
- {{ $filename }}
|
||||
{{ end -}}
|
||||
|
||||
Objects:
|
||||
{{ range $resource, $_ := .Updated.Objects -}}
|
||||
- {{ $resource.Kind | lower }} {{ $resource.Name | lower }}
|
||||
{{ end -}}
|
||||
|
||||
Images:
|
||||
{{ range $image, $_ := .Updated.Images -}}
|
||||
{{ if contains "1.0.0" $image -}}
|
||||
- {{ $image }}
|
||||
{{ else -}}
|
||||
[skip ci] wrong image
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
author:
|
||||
email: fluxcdbot@users.noreply.github.com
|
||||
name: fluxcdbot
|
||||
```
|
||||
There are over 70 available functions. Some of them are defined by the [Go template language](https://pkg.go.dev/text/template) itself. Most of the others are part of the [Sprig template library](http://masterminds.github.io/sprig/).
|
||||
|
||||
### Push
|
||||
|
||||
|
@ -409,51 +326,19 @@ type PushSpec struct {
|
|||
// Branch specifies that commits should be pushed to the branch
|
||||
// named. The branch is created using `.spec.checkout.branch` as the
|
||||
// starting point, if it doesn't already exist.
|
||||
// +optional
|
||||
Branch string `json:"branch,omitempty"`
|
||||
|
||||
// Refspec specifies the Git Refspec to use for a push operation.
|
||||
// If both Branch and Refspec are provided, then the commit is pushed
|
||||
// to the branch and also using the specified refspec.
|
||||
// For more details about Git Refspecs, see:
|
||||
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
|
||||
// +optional
|
||||
Refspec string `json:"refspec,omitempty"`
|
||||
|
||||
// Options specifies the push options that are sent to the Git
|
||||
// server when performing a push operation. For details, see:
|
||||
// https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt
|
||||
Options map[string]string `json:"options,omitempty"`
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
```
|
||||
|
||||
If `.push` is not present, commits are made on the branch given in `.spec.git.checkout.branch` and
|
||||
If `push` is not present, commits are made on the branch given in `.spec.git.checkout.branch` and
|
||||
pushed to the same branch at the origin. If `.spec.git.checkout` is not present, it will fall back
|
||||
to the branch given in the `GitRepository` referenced by `.spec.sourceRef`. If none of these yield a
|
||||
branch name, the automation will fail.
|
||||
|
||||
If `.push.refspec` is present, the refspec specified is used to perform the push operation.
|
||||
An example of a valid refspec is `refs/heads/branch:refs/heads/branch`. This allows users to
|
||||
push to an arbitary destination reference.
|
||||
|
||||
If `.push.branch` is present, the specified branch is pushed to at the origin. The branch
|
||||
When `push` is present, the `branch` field specifies a branch to push to at the origin. The branch
|
||||
will be created locally if it does not already exist, starting from the checkout branch. If it does
|
||||
already exist, it will be overwritten with the cloned version plus the changes made by the
|
||||
controller. Alternatively, force push can be disabled by starting the controller with `--feature-gates=GitForcePushBranch=false`,
|
||||
in which case the updates will be calculated on top of any commits already on the push branch.
|
||||
Note that without force push in push branches, if the target branch is stale, the controller may not
|
||||
be able to conclude the operation and will consistently fail until the branch is either deleted or
|
||||
refreshed.
|
||||
|
||||
If both `.push.refspec` and `.push.branch` are specified, then the reconciler will perform
|
||||
two push operations, one to the specified branch and another using the specified refspec.
|
||||
This is particularly useful for working with Gerrit servers. For more information about this,
|
||||
please refer to the [Gerrit](#gerrit) section.
|
||||
|
||||
**Note:** If both `.push.refspec` and `.push.branch` are essentially equal to
|
||||
each other (for e.g.: `.push.refspec: refs/heads/main:refs/heads/main` and
|
||||
`.push.branch: main`), then the reconciler might fail to perform the second push
|
||||
operation and error out with an `already up-to-date` error.
|
||||
already exist, updates will be calculated on top of any commits already on the branch.
|
||||
|
||||
In the following snippet, updates will be pushed as commits to the branch `auto`, and when that
|
||||
branch does not exist at the origin, it will be created locally starting from the branch `main`, and
|
||||
|
@ -469,117 +354,6 @@ spec:
|
|||
branch: auto
|
||||
```
|
||||
|
||||
In the following snippet, updates and commits will be made on the `main` branch locally.
|
||||
The commits will be then pushed using the `refs/heads/main:refs/heads/auto` refspec:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
git:
|
||||
checkout:
|
||||
ref:
|
||||
branch: main
|
||||
push:
|
||||
refspec: refs/heads/main:refs/heads/auto
|
||||
```
|
||||
|
||||
To specify the [push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt)
|
||||
to be sent to the upstream Git server, use `.push.options`. These options can be
|
||||
used to perform operations as a result of the push. For example, using the below
|
||||
push options will open a GitLab Merge Request to the `release` branch
|
||||
automatically with the commit the controller pushed to the `dev` branch:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
git:
|
||||
push:
|
||||
branch: dev
|
||||
options:
|
||||
merge_request.create: ""
|
||||
merge_request.target: release
|
||||
```
|
||||
|
||||
#### Gerrit
|
||||
|
||||
[Gerrit](https://www.gerritcodereview.com/) operates differently from a
|
||||
standard Git server. Rather than sending individual commits to a branch,
|
||||
all changes are bundled into a single commit. This commit requires a distinct
|
||||
identifier separate from the commit SHA. Additionally, instead of initiating
|
||||
a Pull Request between branches, the commit is pushed using a refspec:
|
||||
`HEAD:refs/for/main`.
|
||||
|
||||
As the image-automation-controller is primarily designed to work with
|
||||
standard Git servers, these special characteristics necessitate a few
|
||||
workarounds. The following is an example configuration that works
|
||||
well with Gerrit:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
git:
|
||||
checkout:
|
||||
ref:
|
||||
branch: main
|
||||
commit:
|
||||
author:
|
||||
email: flux@localdomain
|
||||
name: flux
|
||||
messageTemplate: |
|
||||
Perform automatic image update
|
||||
|
||||
Automation name: {{ .AutomationObject }}
|
||||
|
||||
Files:
|
||||
{{ range $filename, $_ := .Updated.Files -}}
|
||||
- {{ $filename }}
|
||||
{{ end }}
|
||||
Objects:
|
||||
{{ range $resource, $_ := .Updated.Objects -}}
|
||||
- {{ $resource.Kind }} {{ $resource.Name }}
|
||||
{{ end }}
|
||||
Images:
|
||||
{{ range .Updated.Images -}}
|
||||
- {{ . }}
|
||||
{{ end }}
|
||||
{{- $ChangeId := .AutomationObject -}}
|
||||
{{- $ChangeId = printf "%s%s" $ChangeId ( .Updated.Files | toString ) -}}
|
||||
{{- $ChangeId = printf "%s%s" $ChangeId ( .Updated.Objects | toString ) -}}
|
||||
{{- $ChangeId = printf "%s%s" $ChangeId ( .Updated.Images | toString ) }}
|
||||
Change-Id: {{ printf "I%s" ( sha256sum $ChangeId | trunc 40 ) }}
|
||||
push:
|
||||
branch: auto
|
||||
refspec: refs/heads/auto:refs/heads/main
|
||||
```
|
||||
|
||||
This instructs the image-automation-controller to clone the repository using the
|
||||
`main` branch but execute its update logic and commit with the provided message
|
||||
template on the `auto` branch. Commits are then pushed to the `auto` branch,
|
||||
followed by pushing the `HEAD` of the `auto` branch to the `HEAD` of the remote
|
||||
`main` branch. The message template ensures the inclusion of a [Change-Id](https://gerrit-review.googlesource.com/Documentation/concept-changes.html#change-id)
|
||||
at the bottom of the commit message.
|
||||
|
||||
The initial branch push aims to prevent multiple
|
||||
[Patch Sets](https://gerrit-review.googlesource.com/Documentation/concept-patch-sets.html).
|
||||
If we exclude `.push.branch` and only specify
|
||||
`.push.refspec: refs/heads/main:refs/heads/main`, the desired [Change](https://gerrit-review.googlesource.com/Documentation/concept-changes.html)
|
||||
can be created as intended. However, when the controller freshly clones the
|
||||
`main` branch while a Change is open, it executes its update logic on `main`,
|
||||
leading to new commits being pushed with the same changes to the existing open
|
||||
Change. Specifying `.push.branch` circumvents this by instructing the controller
|
||||
to apply the update logic to the `auto` branch, already containing the desired
|
||||
commit. This approach is also recommended in the
|
||||
[Gerrit documentation](https://gerrit-review.googlesource.com/Documentation/intro-gerrit-walkthrough-github.html#create-change).
|
||||
|
||||
Another thing to note is the syntax of `.push.refspec`. Instead of it being
|
||||
`HEAD:refs/for/main`, commonly used by Gerrit users, we specify the full
|
||||
refname `refs/heads/auto` in the source part of the refpsec.
|
||||
|
||||
**Note:** A known limitation of using the image-automation-controller with
|
||||
Gerrit involves handling multiple concurrent Changes. This is due to the
|
||||
calculation of the Change-Id, relying on factors like file names and image
|
||||
tags. If the controller introduces a new file or modifies a previously updated
|
||||
image tag to a different one, it leads to a distinct Change-Id for the commit.
|
||||
Consequently, this action will trigger the creation of an additional Change,
|
||||
even when an existing Change containing outdated modifications remains open.
|
||||
|
||||
## Update strategy
|
||||
|
||||
The `.spec.update` field specifies how to carry out updates on the git repository. There is one
|
||||
|
@ -830,8 +604,8 @@ spec:
|
|||
branch: auto
|
||||
```
|
||||
|
||||
[image-auto-guide]: https://fluxcd.io/flux/guides/image-update/#configure-image-update-for-custom-resources
|
||||
[git-repo-ref]: https://fluxcd.io/flux/components/source/gitrepositories/#writing-a-gitrepository-spec
|
||||
[image-auto-guide]: https://toolkit.fluxcd.io/guides/image-update/#configure-image-update-for-custom-resources
|
||||
[git-repo-ref]: https://toolkit.fluxcd.io/components/source/gitrepositories/#specification
|
||||
[durations]: https://godoc.org/time#ParseDuration
|
||||
[source-docs]: https://fluxcd.io/flux/components/source/api/v1beta2/#source.toolkit.fluxcd.io/v1beta2.GitRepositorySpec
|
||||
[source-docs]: https://toolkit.fluxcd.io/components/source/gitrepositories/#git-implementation
|
||||
[go-text-template]: https://golang.org/pkg/text/template/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
225
go.mod
225
go.mod
|
@ -1,200 +1,39 @@
|
|||
module github.com/fluxcd/image-automation-controller
|
||||
|
||||
go 1.25.0
|
||||
go 1.16
|
||||
|
||||
replace github.com/fluxcd/image-automation-controller/api => ./api
|
||||
|
||||
// Pin kustomize to v5.7.1
|
||||
replace (
|
||||
sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.20.1
|
||||
sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.20.1
|
||||
require (
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7
|
||||
github.com/cyphar/filepath-securejoin v0.2.2
|
||||
github.com/fluxcd/image-automation-controller/api v0.14.1
|
||||
// If you bump this, change REFLECTOR_VER in the Makefile to match
|
||||
github.com/fluxcd/image-reflector-controller/api v0.11.1
|
||||
github.com/fluxcd/pkg/apis/meta v0.10.0
|
||||
github.com/fluxcd/pkg/gittestserver v0.3.1
|
||||
github.com/fluxcd/pkg/runtime v0.12.0
|
||||
github.com/fluxcd/pkg/ssh v0.1.0
|
||||
// If you bump this, change SOURCE_VER in the Makefile to match
|
||||
github.com/fluxcd/source-controller v0.15.4
|
||||
github.com/fluxcd/source-controller/api v0.15.4
|
||||
github.com/go-git/go-billy/v5 v5.3.1
|
||||
github.com/go-git/go-git/v5 v5.4.2
|
||||
github.com/go-logr/logr v0.4.0
|
||||
github.com/google/go-containerregistry v0.6.0
|
||||
github.com/libgit2/git2go/v31 v31.4.14
|
||||
github.com/onsi/ginkgo v1.16.4
|
||||
github.com/onsi/gomega v1.14.0
|
||||
github.com/otiai10/copy v1.2.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
k8s.io/api v0.21.3
|
||||
k8s.io/apimachinery v0.21.3
|
||||
k8s.io/client-go v0.21.3
|
||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e
|
||||
sigs.k8s.io/controller-runtime v0.9.5
|
||||
sigs.k8s.io/kustomize/kyaml v0.10.21
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/ProtonMail/go-crypto v1.3.0
|
||||
github.com/cyphar/filepath-securejoin v0.4.1
|
||||
github.com/fluxcd/image-automation-controller/api v0.41.2
|
||||
github.com/fluxcd/image-reflector-controller/api v0.35.2
|
||||
github.com/fluxcd/pkg/apis/acl v0.9.0
|
||||
github.com/fluxcd/pkg/apis/event v0.19.0
|
||||
github.com/fluxcd/pkg/apis/meta v1.20.0
|
||||
github.com/fluxcd/pkg/auth v0.29.0
|
||||
github.com/fluxcd/pkg/cache v0.11.0
|
||||
github.com/fluxcd/pkg/git v0.36.0
|
||||
github.com/fluxcd/pkg/git/gogit v0.40.0
|
||||
github.com/fluxcd/pkg/gittestserver v0.20.0
|
||||
github.com/fluxcd/pkg/runtime v0.82.0
|
||||
github.com/fluxcd/pkg/ssh v0.21.0
|
||||
github.com/fluxcd/source-controller/api v1.6.2
|
||||
github.com/go-git/go-billy/v5 v5.6.2
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
github.com/go-logr/logr v1.4.3
|
||||
github.com/google/go-containerregistry v0.20.6
|
||||
github.com/onsi/gomega v1.38.2
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/spf13/pflag v1.0.7
|
||||
k8s.io/api v0.34.0
|
||||
k8s.io/apimachinery v0.34.0
|
||||
k8s.io/client-go v0.34.0
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
|
||||
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d
|
||||
sigs.k8s.io/controller-runtime v0.22.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.20.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.16.5 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.8.0 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.50.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.37.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/eks v1.73.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 // indirect
|
||||
github.com/aws/smithy-go v1.23.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/bradleyfalzon/ghinstallation/v2 v2.16.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.3 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v28.2.2+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fluxcd/cli-utils v0.36.0-flux.15 // indirect
|
||||
github.com/fluxcd/gitkit v0.6.0 // indirect
|
||||
github.com/fluxcd/pkg/version v0.10.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-github/v72 v72.0.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // 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/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // 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/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/otiai10/mint v1.6.3 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pjbgf/sha1cd v0.4.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.23.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/spf13/cast v1.8.0 // indirect
|
||||
github.com/spf13/cobra v1.9.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
||||
google.golang.org/api v0.248.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
google.golang.org/protobuf v1.36.7 // 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.v3 v3.0.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.34.0 // indirect
|
||||
k8s.io/cli-runtime v0.34.0 // indirect
|
||||
k8s.io/component-base v0.34.0 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kubectl v0.34.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.20.1 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
// side-effect of depending on source-controller
|
||||
// required by https://github.com/helm/helm/blob/v3.6.0/go.mod
|
||||
replace github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"externalPackages": [
|
||||
{
|
||||
"typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$",
|
||||
"docsURLTemplate": "https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration"
|
||||
"docsURLTemplate": "https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration"
|
||||
},
|
||||
{
|
||||
"typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/",
|
||||
|
@ -17,15 +17,15 @@
|
|||
},
|
||||
{
|
||||
"typeMatchPrefix": "^github.com/fluxcd/pkg/runtime/dependency\\.CrossNamespaceDependencyReference$",
|
||||
"docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/pkg/runtime/dependency#CrossNamespaceDependencyReference"
|
||||
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/runtime/dependency#CrossNamespaceDependencyReference"
|
||||
},
|
||||
{
|
||||
"typeMatchPrefix": "^github.com/fluxcd/pkg/apis/meta",
|
||||
"docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#{{ .TypeIdentifier }}"
|
||||
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/apis/meta#{{ .TypeIdentifier }}"
|
||||
},
|
||||
{
|
||||
"typeMatchPrefix": "^github.com/fluxcd/source-controller/api/v1",
|
||||
"docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/source-controller/api/v1#{{ .TypeIdentifier }}"
|
||||
"typeMatchPrefix": "^github.com/fluxcd/source-controller/api/v1beta1",
|
||||
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/source-controller/api/v1beta1#{{ .TypeIdentifier }}"
|
||||
}
|
||||
],
|
||||
"typeDisplayNamePrefixOverrides": {
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
{{ define "packages" }}
|
||||
<h1>Image update automation API reference
|
||||
{{- with (index .packages 0) -}}
|
||||
{{ with (index .GoPackages 0 ) -}}
|
||||
{{ printf " %s" .Name -}}
|
||||
{{ end -}}
|
||||
{{ end }}</h1>
|
||||
<h1>Image update automation API reference</h1>
|
||||
|
||||
{{ with .packages}}
|
||||
<p>Packages:</p>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2025 The Flux authors
|
||||
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.
|
||||
|
|
|
@ -1,523 +0,0 @@
|
|||
//go:build gofuzz_libfuzzer
|
||||
// +build gofuzz_libfuzzer
|
||||
|
||||
/*
|
||||
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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
fuzz "github.com/AdaLogics/go-fuzz-headers"
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
"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/storage/memory"
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
|
||||
image_reflectv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
"github.com/fluxcd/pkg/gittestserver"
|
||||
"github.com/fluxcd/pkg/runtime/controller"
|
||||
"github.com/fluxcd/pkg/runtime/testenv"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
image_automationv1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/update"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFuzz *rest.Config
|
||||
k8sClient client.Client
|
||||
imageAutoReconcilerFuzz *ImageUpdateAutomationReconciler
|
||||
testEnvFuzz *testenv.Environment
|
||||
initter sync.Once
|
||||
)
|
||||
|
||||
const defaultBinVersion = "1.24"
|
||||
|
||||
//go:embed testdata/crd
|
||||
var testFiles embed.FS
|
||||
|
||||
// This fuzzer randomized 2 things:
|
||||
// 1: The files in the git repository
|
||||
// 2: The values of ImageUpdateAutomationSpec
|
||||
//
|
||||
// and ImagePolicy resources
|
||||
func Fuzz_ImageUpdateReconciler(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed []byte) {
|
||||
initter.Do(func() {
|
||||
utilruntime.Must(ensureDependencies(func(m manager.Manager) {
|
||||
utilruntime.Must((&ImageUpdateAutomationReconciler{
|
||||
Client: m.GetClient(),
|
||||
}).SetupWithManager(context.TODO(), m, ImageUpdateAutomationReconcilerOptions{
|
||||
RateLimiter: controller.GetDefaultRateLimiter(),
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
f := fuzz.NewConsumer(seed)
|
||||
|
||||
// We start by creating a lot of the values that
|
||||
// need for the various resources later on
|
||||
runes := "abcdefghijklmnopqrstuvwxyz1234567890"
|
||||
branch, err := f.GetStringFrom(runes, 80)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
repPath, err := f.GetStringFrom(runes, 80)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
repositoryPath := "/config-" + repPath + ".git"
|
||||
|
||||
namespaceName, err := f.GetStringFrom(runes, 59)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gitRepoKeyName, err := f.GetStringFrom(runes, 80)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
username, err := f.GetStringFrom(runes, 80)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
password, err := f.GetStringFrom(runes, 80)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ipSpec := image_reflectv1.ImagePolicySpec{}
|
||||
err = f.GenerateStruct(&ipSpec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ipStatus := image_reflectv1.ImagePolicyStatus{}
|
||||
err = f.GenerateStruct(&ipStatus)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
iuaSpec := image_automationv1.ImageUpdateAutomationSpec{}
|
||||
err = f.GenerateStruct(&iuaSpec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
gitSpec := &image_automationv1.GitSpec{}
|
||||
err = f.GenerateStruct(&gitSpec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
policyKeyName, err := f.GetStringFrom(runes, 80)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
updateKeyName, err := f.GetStringFrom("abcdefghijklmnopqrstuvwxy.-", 120)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create random git files
|
||||
gitPath, err := os.MkdirTemp("", "git-dir-")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(gitPath)
|
||||
err = f.CreateFiles(gitPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Done with creating the random values
|
||||
|
||||
// Create a namespace
|
||||
namespace := &corev1.Namespace{}
|
||||
namespace.Name = namespaceName
|
||||
err = k8sClient.Create(context.Background(), namespace)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
err = k8sClient.Delete(context.Background(), namespace)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
time.Sleep(80 * time.Millisecond)
|
||||
}()
|
||||
|
||||
// Set up git-related stuff
|
||||
gitServer, err := gittestserver.NewTempGitServer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
gitServer.Auth(username, password)
|
||||
gitServer.AutoCreate()
|
||||
err = gitServer.StartHTTP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
gitServer.StopHTTP()
|
||||
os.RemoveAll(gitServer.Root())
|
||||
}()
|
||||
gitServer.KeyDir(filepath.Join(gitServer.Root(), "keys"))
|
||||
err = gitServer.ListenSSH()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = initGitRepo(gitServer, gitPath, branch, repositoryPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
repoURL := gitServer.HTTPAddressWithCredentials() + repositoryPath
|
||||
// Done with setting up git related stuff
|
||||
|
||||
// Create git repository object
|
||||
gitRepoKey := types.NamespacedName{
|
||||
Name: "image-auto-" + gitRepoKeyName,
|
||||
Namespace: namespace.Name,
|
||||
}
|
||||
|
||||
gitRepo := &sourcev1.GitRepository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: gitRepoKey.Name,
|
||||
Namespace: namespace.Name,
|
||||
},
|
||||
Spec: sourcev1.GitRepositorySpec{
|
||||
URL: repoURL,
|
||||
Interval: metav1.Duration{Duration: time.Minute},
|
||||
},
|
||||
}
|
||||
err = k8sClient.Create(context.Background(), gitRepo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer k8sClient.Delete(context.Background(), gitRepo)
|
||||
|
||||
// Create image policy object
|
||||
policyKey := types.NamespacedName{
|
||||
Name: "policy-" + policyKeyName,
|
||||
Namespace: namespace.Name,
|
||||
}
|
||||
policy := &image_reflectv1.ImagePolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: policyKey.Name,
|
||||
Namespace: policyKey.Namespace,
|
||||
},
|
||||
Spec: ipSpec,
|
||||
Status: ipStatus,
|
||||
}
|
||||
err = k8sClient.Create(context.Background(), policy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = k8sClient.Status().Update(context.Background(), policy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create ImageUpdateAutomation object
|
||||
updateKey := types.NamespacedName{
|
||||
Namespace: namespace.Name,
|
||||
Name: updateKeyName,
|
||||
}
|
||||
|
||||
// Setting these fields manually to help the fuzzer
|
||||
gitSpec.Checkout.Reference.Branch = branch
|
||||
iuaSpec.GitSpec = gitSpec
|
||||
iuaSpec.SourceRef.Kind = "GitRepository"
|
||||
iuaSpec.SourceRef.Name = gitRepoKey.Name
|
||||
iuaSpec.Update.Strategy = image_automationv1.UpdateStrategySetters
|
||||
|
||||
iua := &image_automationv1.ImageUpdateAutomation{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: updateKey.Name,
|
||||
Namespace: updateKey.Namespace,
|
||||
},
|
||||
Spec: iuaSpec,
|
||||
}
|
||||
err = k8sClient.Create(context.Background(), iua)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer k8sClient.Delete(context.Background(), iua)
|
||||
time.Sleep(time.Millisecond * 70)
|
||||
})
|
||||
}
|
||||
|
||||
// A fuzzer that is more focused on UpdateWithSetters
|
||||
// that the reconciler fuzzer is
|
||||
func FuzzUpdateWithSetters(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, seed []byte) {
|
||||
f := fuzz.NewConsumer(seed)
|
||||
|
||||
// Create dir1
|
||||
tmp1, err := ioutil.TempDir("", "fuzztest1")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tmp1)
|
||||
// Add files to dir1
|
||||
err = f.CreateFiles(tmp1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create dir2
|
||||
tmp2, err := ioutil.TempDir("", "fuzztest2")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tmp2)
|
||||
|
||||
// Create policies
|
||||
policies := make([]image_reflectv1.ImagePolicy, 0)
|
||||
noOfPolicies, err := f.GetInt()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := 0; i < noOfPolicies%10; i++ {
|
||||
policy := image_reflectv1.ImagePolicy{}
|
||||
err = f.GenerateStruct(&policy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
policies = append(policies, policy)
|
||||
}
|
||||
|
||||
_, _ = update.UpdateWithSetters(logr.Discard(), tmp1, tmp2, policies)
|
||||
})
|
||||
}
|
||||
|
||||
// Initialise a git server with a repo including the files in dir.
|
||||
func initGitRepo(gitServer *gittestserver.GitServer, fixture, branch, repositoryPath string) error {
|
||||
fs := memfs.New()
|
||||
repo, err := gogit.Init(memory.NewStorage(), fs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = populateRepoFromFixture(repo, fixture)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
working, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = working.Checkout(&gogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
Create: true,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remote, err := repo.CreateRemote(&config.RemoteConfig{
|
||||
Name: "origin",
|
||||
URLs: []string{gitServer.HTTPAddressWithCredentials() + repositoryPath},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return remote.Push(&gogit.PushOptions{
|
||||
RefSpecs: []config.RefSpec{
|
||||
config.RefSpec(fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func populateRepoFromFixture(repo *gogit.Repository, fixture string) error {
|
||||
working, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs := working.Filesystem
|
||||
|
||||
if err = filepath.Walk(fixture, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fs.MkdirAll(fs.Join(path[len(fixture):]), info.Mode())
|
||||
}
|
||||
// copy symlinks as-is, so I can test what happens with broken symlinks
|
||||
if info.Mode()&os.ModeSymlink > 0 {
|
||||
target, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.Symlink(target, path[len(fixture):])
|
||||
}
|
||||
|
||||
fileBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ff, err := fs.Create(path[len(fixture):])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ff.Close()
|
||||
|
||||
_, err = ff.Write(fileBytes)
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = working.Add(".")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = working.Commit("Initial revision from "+fixture, &gogit.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: "Testbot",
|
||||
Email: "test@example.com",
|
||||
When: time.Now(),
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func envtestBinVersion() string {
|
||||
if binVersion := os.Getenv("ENVTEST_BIN_VERSION"); binVersion != "" {
|
||||
return binVersion
|
||||
}
|
||||
return defaultBinVersion
|
||||
}
|
||||
|
||||
func ensureDependencies(setupReconcilers func(manager.Manager)) error {
|
||||
if _, err := os.Stat("/.dockerenv"); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if os.Getenv("KUBEBUILDER_ASSETS") == "" {
|
||||
binVersion := envtestBinVersion()
|
||||
cmd := exec.Command("/usr/bin/bash", "-c", fmt.Sprintf(`go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest && \
|
||||
/root/go/bin/setup-envtest use -p path %s`, binVersion))
|
||||
|
||||
cmd.Env = append(os.Environ(), "GOPATH=/root/go")
|
||||
assetsPath, err := cmd.Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Setenv("KUBEBUILDER_ASSETS", string(assetsPath))
|
||||
}
|
||||
|
||||
// Output all embedded testdata files
|
||||
embedDirs := []string{"testdata/crd"}
|
||||
for _, dir := range embedDirs {
|
||||
err := os.MkdirAll(dir, 0o755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mkdir %s: %v", dir, err)
|
||||
}
|
||||
|
||||
templates, err := fs.ReadDir(testFiles, dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading embedded dir: %v", err)
|
||||
}
|
||||
|
||||
for _, template := range templates {
|
||||
fileName := fmt.Sprintf("%s/%s", dir, template.Name())
|
||||
fmt.Println(fileName)
|
||||
|
||||
data, err := testFiles.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading embedded file %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
os.WriteFile(fileName, data, 0o644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing %s: %v", fileName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testEnv := &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{
|
||||
filepath.Join("testdata", "crds"),
|
||||
},
|
||||
}
|
||||
fmt.Println("Starting the test environment")
|
||||
cfg, err := testEnv.Start()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to start the test environment manager: %v", err))
|
||||
}
|
||||
|
||||
utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme))
|
||||
utilruntime.Must(image_reflectv1.AddToScheme(scheme.Scheme))
|
||||
utilruntime.Must(image_automationv1.AddToScheme(scheme.Scheme))
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if k8sClient == nil {
|
||||
panic("cfg is nil but should not be")
|
||||
}
|
||||
|
||||
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
|
||||
Scheme: scheme.Scheme,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
setupReconcilers(k8sManager)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
go func() {
|
||||
fmt.Println("Starting k8sManager...")
|
||||
utilruntime.Must(k8sManager.Start(context.TODO()))
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,654 +0,0 @@
|
|||
/*
|
||||
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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
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/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
kuberecorder "k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
aclapi "github.com/fluxcd/pkg/apis/acl"
|
||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/runtime/acl"
|
||||
"github.com/fluxcd/pkg/runtime/conditions"
|
||||
helper "github.com/fluxcd/pkg/runtime/controller"
|
||||
"github.com/fluxcd/pkg/runtime/patch"
|
||||
"github.com/fluxcd/pkg/runtime/predicates"
|
||||
runtimereconcile "github.com/fluxcd/pkg/runtime/reconcile"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/features"
|
||||
"github.com/fluxcd/image-automation-controller/internal/policy"
|
||||
"github.com/fluxcd/image-automation-controller/internal/source"
|
||||
)
|
||||
|
||||
const repoRefKey = ".spec.gitRepository"
|
||||
|
||||
const readyMessage = "repository up-to-date"
|
||||
|
||||
// imageUpdateAutomationOwnedConditions is a list of conditions owned by the
|
||||
// ImageUpdateAutomationReconciler.
|
||||
var imageUpdateAutomationOwnedConditions = []string{
|
||||
meta.ReadyCondition,
|
||||
meta.ReconcilingCondition,
|
||||
meta.StalledCondition,
|
||||
}
|
||||
|
||||
// imageUpdateAutomationNegativeConditions is a list of negative polarity
|
||||
// conditions owned by ImageUpdateAutomationReconciler. It is used in tests for
|
||||
// compliance with kstatus.
|
||||
var imageUpdateAutomationNegativeConditions = []string{
|
||||
meta.StalledCondition,
|
||||
meta.ReconcilingCondition,
|
||||
}
|
||||
|
||||
var errParsePolicySelector = errors.New("failed to parse policy selector")
|
||||
|
||||
// getPatchOptions composes patch options based on the given parameters.
|
||||
// It is used as the options used when patching an object.
|
||||
func getPatchOptions(ownedConditions []string, controllerName string) []patch.Option {
|
||||
return []patch.Option{
|
||||
patch.WithOwnedConditions{Conditions: ownedConditions},
|
||||
patch.WithFieldOwner(controllerName),
|
||||
}
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imageupdateautomations,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imageupdateautomations/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imagepolicies,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imagepolicies/status,verbs=get
|
||||
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=gitrepositories,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
|
||||
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
|
||||
|
||||
// ImageUpdateAutomationReconciler reconciles a ImageUpdateAutomation object
|
||||
type ImageUpdateAutomationReconciler struct {
|
||||
client.Client
|
||||
kuberecorder.EventRecorder
|
||||
helper.Metrics
|
||||
|
||||
ControllerName string
|
||||
NoCrossNamespaceRef bool
|
||||
|
||||
features map[string]bool
|
||||
|
||||
patchOptions []patch.Option
|
||||
|
||||
tokenCache *cache.TokenCache
|
||||
}
|
||||
|
||||
type ImageUpdateAutomationReconcilerOptions struct {
|
||||
MaxConcurrentReconciles int
|
||||
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
|
||||
RecoverPanic bool
|
||||
TokenCache *cache.TokenCache
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts ImageUpdateAutomationReconcilerOptions) error {
|
||||
r.patchOptions = getPatchOptions(imageUpdateAutomationOwnedConditions, r.ControllerName)
|
||||
|
||||
if r.features == nil {
|
||||
r.features = features.FeatureGates()
|
||||
}
|
||||
|
||||
r.tokenCache = opts.TokenCache
|
||||
|
||||
// Index the git repository object that each I-U-A refers to
|
||||
if err := mgr.GetFieldIndexer().IndexField(ctx, &imagev1.ImageUpdateAutomation{}, repoRefKey, func(obj client.Object) []string {
|
||||
updater := obj.(*imagev1.ImageUpdateAutomation)
|
||||
ref := updater.Spec.SourceRef
|
||||
ns := ref.Namespace
|
||||
if ns == "" {
|
||||
ns = obj.GetNamespace()
|
||||
}
|
||||
return []string{fmt.Sprintf("%s/%s", ns, ref.Name)}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&imagev1.ImageUpdateAutomation{}, builder.WithPredicates(
|
||||
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}))).
|
||||
Watches(
|
||||
&sourcev1.GitRepository{},
|
||||
handler.EnqueueRequestsFromMapFunc(r.automationsForGitRepo),
|
||||
builder.WithPredicates(sourceConfigChangePredicate{}),
|
||||
).
|
||||
Watches(
|
||||
&reflectorv1.ImagePolicy{},
|
||||
handler.EnqueueRequestsFromMapFunc(r.automationsForImagePolicy),
|
||||
builder.WithPredicates(latestImageChangePredicate{}),
|
||||
).
|
||||
WithOptions(controller.Options{
|
||||
RateLimiter: opts.RateLimiter,
|
||||
}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// automationsForGitRepo fetches all the automations that refer to a
|
||||
// particular source.GitRepository object.
|
||||
func (r *ImageUpdateAutomationReconciler) automationsForGitRepo(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
var autoList imagev1.ImageUpdateAutomationList
|
||||
objKey := fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName())
|
||||
if err := r.List(ctx, &autoList, client.MatchingFields{repoRefKey: objKey}); err != nil {
|
||||
ctrl.LoggerFrom(ctx).Error(err, "failed to list ImageUpdateAutomations for GitRepository change")
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, len(autoList.Items))
|
||||
for i := range autoList.Items {
|
||||
reqs[i].NamespacedName.Name = autoList.Items[i].GetName()
|
||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
|
||||
// automationsForImagePolicy fetches all the automation objects that
|
||||
// might depend on a image policy object. Since the link is via
|
||||
// markers in the git repo, _any_ automation object in the same
|
||||
// namespace could be affected.
|
||||
func (r *ImageUpdateAutomationReconciler) automationsForImagePolicy(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
var autoList imagev1.ImageUpdateAutomationList
|
||||
if err := r.List(ctx, &autoList, client.InNamespace(obj.GetNamespace())); err != nil {
|
||||
ctrl.LoggerFrom(ctx).Error(err, "failed to list ImageUpdateAutomations for ImagePolicy change")
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, len(autoList.Items))
|
||||
for i := range autoList.Items {
|
||||
reqs[i].NamespacedName.Name = autoList.Items[i].GetName()
|
||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
|
||||
start := time.Now()
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
// Fetch the ImageUpdateAutomation.
|
||||
obj := &imagev1.ImageUpdateAutomation{}
|
||||
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
// Initialize the patch helper with the current version of the object.
|
||||
serialPatcher := patch.NewSerialPatcher(obj, r.Client)
|
||||
|
||||
// Always attempt to patch the object after each reconciliation.
|
||||
defer func() {
|
||||
// Create patch options for the final patch of the object.
|
||||
patchOpts := runtimereconcile.AddPatchOptions(obj, r.patchOptions, imageUpdateAutomationOwnedConditions, r.ControllerName)
|
||||
if err := serialPatcher.Patch(ctx, obj, patchOpts...); err != nil {
|
||||
// Ignore patch error "not found" when the object is being deleted.
|
||||
if !obj.GetDeletionTimestamp().IsZero() {
|
||||
err = kerrors.FilterOut(err, func(e error) bool { return apierrors.IsNotFound(e) })
|
||||
}
|
||||
retErr = kerrors.NewAggregate([]error{retErr, err})
|
||||
}
|
||||
|
||||
// When the reconciliation ends with an error, ensure that the Result is
|
||||
// empty. This is to suppress the runtime warning when returning a
|
||||
// non-zero Result and an error.
|
||||
if retErr != nil {
|
||||
result = ctrl.Result{}
|
||||
}
|
||||
|
||||
// Always record suspend, readiness and duration metrics.
|
||||
r.Metrics.RecordDuration(ctx, obj, start)
|
||||
}()
|
||||
|
||||
// Examine if the object is under deletion.
|
||||
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
return r.reconcileDelete(obj)
|
||||
}
|
||||
|
||||
// Add finalizer first if it doesn't exist to avoid the race condition
|
||||
// between init and delete.
|
||||
// Note: Finalizers in general can only be added when the deletionTimestamp
|
||||
// is not set.
|
||||
if !controllerutil.ContainsFinalizer(obj, imagev1.ImageUpdateAutomationFinalizer) {
|
||||
controllerutil.AddFinalizer(obj, imagev1.ImageUpdateAutomationFinalizer)
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
|
||||
// Return if the object is suspended.
|
||||
if obj.Spec.Suspend {
|
||||
log.Info("reconciliation is suspended for this object")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
result, retErr = r.reconcile(ctx, serialPatcher, obj, start)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) reconcile(ctx context.Context, sp *patch.SerialPatcher,
|
||||
obj *imagev1.ImageUpdateAutomation, startTime time.Time) (result ctrl.Result, retErr error) {
|
||||
oldObj := obj.DeepCopy()
|
||||
|
||||
var pushResult *source.PushResult
|
||||
|
||||
// syncNeeded decides if full reconciliation with image update is needed.
|
||||
syncNeeded := false
|
||||
|
||||
defer func() {
|
||||
// Define the meaning of success based on the requeue interval.
|
||||
isSuccess := func(res ctrl.Result, err error) bool {
|
||||
if err != nil || res.RequeueAfter != obj.GetRequeueAfter() || res.Requeue {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
rs := runtimereconcile.NewResultFinalizer(isSuccess, readyMessage)
|
||||
retErr = rs.Finalize(obj, result, retErr)
|
||||
|
||||
// Presence of reconciling means that the reconciliation didn't succeed.
|
||||
// Set the Reconciling reason to ProgressingWithRetry to indicate a
|
||||
// failure retry.
|
||||
if conditions.IsReconciling(obj) {
|
||||
reconciling := conditions.Get(obj, meta.ReconcilingCondition)
|
||||
reconciling.Reason = meta.ProgressingWithRetryReason
|
||||
conditions.Set(obj, reconciling)
|
||||
}
|
||||
|
||||
r.notify(ctx, oldObj, obj, pushResult, syncNeeded)
|
||||
}()
|
||||
|
||||
// TODO: Maybe move this to Reconcile()'s defer and avoid passing startTime
|
||||
// to reconcile()?
|
||||
obj.Status.LastAutomationRunTime = &metav1.Time{Time: startTime}
|
||||
|
||||
// Set reconciling condition.
|
||||
runtimereconcile.ProgressiveStatus(false, obj, meta.ProgressingReason, "reconciliation in progress")
|
||||
|
||||
var reconcileAtVal string
|
||||
if v, ok := meta.ReconcileAnnotationValue(obj.GetAnnotations()); ok {
|
||||
reconcileAtVal = v
|
||||
}
|
||||
|
||||
// Persist reconciling if generation differs or reconciliation is requested.
|
||||
switch {
|
||||
case obj.Generation != obj.Status.ObservedGeneration:
|
||||
runtimereconcile.ProgressiveStatus(false, obj, meta.ProgressingReason,
|
||||
"processing object: new generation %d -> %d", obj.Status.ObservedGeneration, obj.Generation)
|
||||
if err := sp.Patch(ctx, obj, r.patchOptions...); err != nil {
|
||||
result, retErr = ctrl.Result{}, err
|
||||
return
|
||||
}
|
||||
case reconcileAtVal != obj.Status.GetLastHandledReconcileRequest():
|
||||
if err := sp.Patch(ctx, obj, r.patchOptions...); err != nil {
|
||||
result, retErr = ctrl.Result{}, err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// List the policies and construct observed policies.
|
||||
policies, err := getPolicies(ctx, r.Client, obj.Namespace, obj.Spec.PolicySelector)
|
||||
if err != nil {
|
||||
if errors.Is(err, errParsePolicySelector) {
|
||||
conditions.MarkStalled(obj, imagev1.InvalidPolicySelectorReason, "%s", err)
|
||||
result, retErr = ctrl.Result{}, nil
|
||||
return
|
||||
}
|
||||
result, retErr = ctrl.Result{}, err
|
||||
return
|
||||
}
|
||||
// Update any stale Ready=False condition from policies config failure.
|
||||
if conditions.HasAnyReason(obj, meta.ReadyCondition, imagev1.InvalidPolicySelectorReason) {
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
|
||||
}
|
||||
|
||||
// Index the policies by their name.
|
||||
observedPolicies := imagev1.ObservedPolicies{}
|
||||
for _, policy := range policies {
|
||||
observedPolicies[policy.Name] = imagev1.ImageRef{
|
||||
Name: policy.Status.LatestRef.Name,
|
||||
Tag: policy.Status.LatestRef.Tag,
|
||||
Digest: policy.Status.LatestRef.Digest,
|
||||
}
|
||||
}
|
||||
|
||||
// If the policies have changed, require a full sync.
|
||||
if observedPoliciesChanged(obj.Status.ObservedPolicies, observedPolicies) {
|
||||
syncNeeded = true
|
||||
}
|
||||
|
||||
// Create source manager with options.
|
||||
smOpts := []source.SourceOption{
|
||||
source.WithSourceOptionInvolvedObject(obj.GetName(), obj.GetNamespace()),
|
||||
source.WithSourceOptionTokenCache(r.tokenCache),
|
||||
}
|
||||
if r.NoCrossNamespaceRef {
|
||||
smOpts = append(smOpts, source.WithSourceOptionNoCrossNamespaceRef())
|
||||
}
|
||||
if r.features[features.GitAllBranchReferences] {
|
||||
smOpts = append(smOpts, source.WithSourceOptionGitAllBranchReferences())
|
||||
}
|
||||
sm, err := source.NewSourceManager(ctx, r.Client, obj, smOpts...)
|
||||
if err != nil {
|
||||
if acl.IsAccessDenied(err) {
|
||||
conditions.MarkStalled(obj, aclapi.AccessDeniedReason, "%s", err)
|
||||
result, retErr = ctrl.Result{}, nil
|
||||
return
|
||||
}
|
||||
if errors.Is(err, source.ErrInvalidSourceConfiguration) {
|
||||
conditions.MarkStalled(obj, imagev1.InvalidSourceConfigReason, "%s", err)
|
||||
result, retErr = ctrl.Result{}, nil
|
||||
return
|
||||
}
|
||||
e := fmt.Errorf("failed configuring source manager: %w", err)
|
||||
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.SourceManagerFailedReason, "%s", e)
|
||||
result, retErr = ctrl.Result{}, e
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := sm.Cleanup(); err != nil {
|
||||
retErr = err
|
||||
}
|
||||
}()
|
||||
// Update any stale Ready=False condition from SourceManager failure.
|
||||
if conditions.HasAnyReason(obj, meta.ReadyCondition, aclapi.AccessDeniedCondition, imagev1.InvalidSourceConfigReason, imagev1.SourceManagerFailedReason) {
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
|
||||
}
|
||||
|
||||
// When the checkout and push branches are different or a refspec is
|
||||
// defined, always perform a full sync.
|
||||
// This can be worked around in the future by also querying the HEAD of push
|
||||
// branch to detech if it has drifted.
|
||||
if sm.SwitchBranch() || obj.Spec.GitSpec.HasRefspec() {
|
||||
syncNeeded = true
|
||||
}
|
||||
|
||||
// Build checkout options.
|
||||
checkoutOpts := []source.CheckoutOption{}
|
||||
if r.features[features.GitShallowClone] {
|
||||
checkoutOpts = append(checkoutOpts, source.WithCheckoutOptionShallowClone())
|
||||
}
|
||||
if r.features[features.GitSparseCheckout] && obj.Spec.Update.Path != "" {
|
||||
checkoutOpts = append(checkoutOpts, source.WithCheckoutOptionSparseCheckoutDirectories(obj.Spec.Update.Path))
|
||||
}
|
||||
|
||||
// If full sync is still not needed, configure last observed commit to
|
||||
// perform optimized clone and obtain a non-concrete commit if the remote
|
||||
// has not changed.
|
||||
if !syncNeeded && obj.Status.ObservedSourceRevision != "" {
|
||||
checkoutOpts = append(checkoutOpts, source.WithCheckoutOptionLastObserved(obj.Status.ObservedSourceRevision))
|
||||
}
|
||||
|
||||
commit, err := sm.CheckoutSource(ctx, checkoutOpts...)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("failed to checkout source: %w", err)
|
||||
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.GitOperationFailedReason, "%s", e)
|
||||
result, retErr = ctrl.Result{}, e
|
||||
return
|
||||
}
|
||||
// Update any stale Ready=False condition from checkout failure.
|
||||
if conditions.HasAnyReason(obj, meta.ReadyCondition, imagev1.GitOperationFailedReason) {
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
|
||||
}
|
||||
|
||||
// If it's a partial commit, the reconciliation can be skipped. The last
|
||||
// observed commit is only configured above when full sync is not needed.
|
||||
// No change in the policies and remote git repository. Skip reconciliation.
|
||||
if !git.IsConcreteCommit(*commit) {
|
||||
// Remove any stale Ready condition, most likely False, set above. Its value
|
||||
// is derived from the overall result of the reconciliation in the deferred
|
||||
// block at the very end.
|
||||
conditions.Delete(obj, meta.ReadyCondition)
|
||||
result, retErr = ctrl.Result{RequeueAfter: obj.GetRequeueAfter()}, nil
|
||||
return
|
||||
} else {
|
||||
// Concrete commit indicates full sync is needed due to new remote
|
||||
// revision.
|
||||
syncNeeded = true
|
||||
}
|
||||
// Continue with full sync with a concrete commit.
|
||||
|
||||
// Apply the policies and check if there's anything to update.
|
||||
policyResult, err := policy.ApplyPolicies(ctx, sm.WorkDirectory(), obj, policies)
|
||||
if err != nil {
|
||||
if errors.Is(err, policy.ErrNoUpdateStrategy) || errors.Is(err, policy.ErrUnsupportedUpdateStrategy) {
|
||||
conditions.MarkStalled(obj, imagev1.InvalidUpdateStrategyReason, "%s", err)
|
||||
result, retErr = ctrl.Result{}, nil
|
||||
return
|
||||
}
|
||||
e := fmt.Errorf("failed to apply policies: %w", err)
|
||||
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.UpdateFailedReason, "%s", e)
|
||||
result, retErr = ctrl.Result{}, e
|
||||
return
|
||||
}
|
||||
// Update any stale Ready=False condition from apply policies failure.
|
||||
if conditions.HasAnyReason(obj, meta.ReadyCondition, imagev1.InvalidUpdateStrategyReason, imagev1.UpdateFailedReason) {
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
|
||||
}
|
||||
|
||||
if len(policyResult.FileChanges) == 0 {
|
||||
// Remove any stale Ready condition, most likely False, set above. Its
|
||||
// value is derived from the overall result of the reconciliation in the
|
||||
// deferred block at the very end.
|
||||
conditions.Delete(obj, meta.ReadyCondition)
|
||||
|
||||
// Persist observations.
|
||||
obj.Status.ObservedSourceRevision = commit.String()
|
||||
obj.Status.ObservedPolicies = observedPolicies
|
||||
|
||||
result, retErr = ctrl.Result{RequeueAfter: obj.GetRequeueAfter()}, nil
|
||||
return
|
||||
}
|
||||
|
||||
// Build push config.
|
||||
pushCfg := []source.PushConfig{}
|
||||
// Enable force only when branch is changed for push.
|
||||
if r.features[features.GitForcePushBranch] && sm.SwitchBranch() {
|
||||
pushCfg = append(pushCfg, source.WithPushConfigForce())
|
||||
}
|
||||
// Include any push options.
|
||||
if obj.Spec.GitSpec.Push != nil && obj.Spec.GitSpec.Push.Options != nil {
|
||||
pushCfg = append(pushCfg, source.WithPushConfigOptions(obj.Spec.GitSpec.Push.Options))
|
||||
}
|
||||
|
||||
pushResult, err = sm.CommitAndPush(ctx, obj, policyResult, pushCfg...)
|
||||
if err != nil {
|
||||
// Check if error is due to removed template field usage.
|
||||
// Set Stalled condition and return nil error to prevent requeue, allowing user to fix template.
|
||||
if errors.Is(err, source.ErrRemovedTemplateField) {
|
||||
conditions.MarkStalled(obj, imagev1.RemovedTemplateFieldReason, "%s", err)
|
||||
result, retErr = ctrl.Result{}, nil
|
||||
return
|
||||
}
|
||||
|
||||
e := fmt.Errorf("failed to update source: %w", err)
|
||||
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.GitOperationFailedReason, "%s", e)
|
||||
result, retErr = ctrl.Result{}, e
|
||||
return
|
||||
}
|
||||
// Update any stale Ready=False condition from commit and push failure.
|
||||
if conditions.HasAnyReason(obj, meta.ReadyCondition, imagev1.GitOperationFailedReason) {
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
|
||||
}
|
||||
|
||||
if pushResult == nil {
|
||||
// NOTE: This should not happen. This exists as a legacy behavior from
|
||||
// the old implementation where no commit is made due to no stagged
|
||||
// files. If nothing is pushed, the repository is up-to-date. Persist
|
||||
// observations and return with successful result.
|
||||
conditions.Delete(obj, meta.ReadyCondition)
|
||||
obj.Status.ObservedSourceRevision = commit.String()
|
||||
obj.Status.ObservedPolicies = observedPolicies
|
||||
result, retErr = ctrl.Result{RequeueAfter: obj.GetRequeueAfter()}, nil
|
||||
return
|
||||
}
|
||||
|
||||
// Persist observations.
|
||||
obj.Status.ObservedSourceRevision = pushResult.Commit().String()
|
||||
// If the push branch is different, store the checkout branch commit as the
|
||||
// observed source revision.
|
||||
if pushResult.SwitchBranch() {
|
||||
obj.Status.ObservedSourceRevision = commit.String()
|
||||
}
|
||||
obj.Status.ObservedPolicies = observedPolicies
|
||||
obj.Status.LastPushCommit = pushResult.Commit().Hash.String()
|
||||
obj.Status.LastPushTime = pushResult.Time()
|
||||
|
||||
// Remove any stale Ready condition, most likely False, set above. Its value
|
||||
// is derived from the overall result of the reconciliation in the deferred
|
||||
// block at the very end.
|
||||
conditions.Delete(obj, meta.ReadyCondition)
|
||||
result, retErr = ctrl.Result{RequeueAfter: obj.GetRequeueAfter()}, nil
|
||||
return
|
||||
}
|
||||
|
||||
// reconcileDelete handles the deletion of the object.
|
||||
func (r *ImageUpdateAutomationReconciler) reconcileDelete(obj *imagev1.ImageUpdateAutomation) (ctrl.Result, error) {
|
||||
// Remove our finalizer from the list.
|
||||
controllerutil.RemoveFinalizer(obj, imagev1.ImageUpdateAutomationFinalizer)
|
||||
|
||||
// Cleanup caches.
|
||||
r.tokenCache.DeleteEventsForObject(imagev1.ImageUpdateAutomationKind,
|
||||
obj.GetName(), obj.GetNamespace(), cache.OperationReconcile)
|
||||
|
||||
// Stop reconciliation as the object is being deleted.
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// getPolicies returns list of policies in the given namespace that have latest
|
||||
// image.
|
||||
func getPolicies(ctx context.Context, kclient client.Client, namespace string, selector *metav1.LabelSelector) ([]reflectorv1.ImagePolicy, error) {
|
||||
policySelector := labels.Everything()
|
||||
var err error
|
||||
if selector != nil {
|
||||
if policySelector, err = metav1.LabelSelectorAsSelector(selector); err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errParsePolicySelector, err)
|
||||
}
|
||||
}
|
||||
|
||||
var policies reflectorv1.ImagePolicyList
|
||||
if err := kclient.List(ctx, &policies, &client.ListOptions{Namespace: namespace, LabelSelector: policySelector}); err != nil {
|
||||
return nil, fmt.Errorf("failed to list policies: %w", err)
|
||||
}
|
||||
|
||||
readyPolicies := []reflectorv1.ImagePolicy{}
|
||||
for _, policy := range policies.Items {
|
||||
// Ignore the policies that don't have a latest image.
|
||||
if policy.Status.LatestRef == nil {
|
||||
continue
|
||||
}
|
||||
readyPolicies = append(readyPolicies, policy)
|
||||
}
|
||||
|
||||
return readyPolicies, nil
|
||||
}
|
||||
|
||||
// observedPoliciesChanged returns if the previous and current observedPolicies
|
||||
// have changed.
|
||||
func observedPoliciesChanged(previous, current imagev1.ObservedPolicies) bool {
|
||||
if len(previous) != len(current) {
|
||||
return true
|
||||
}
|
||||
for name, imageRef := range current {
|
||||
oldImageRef, ok := previous[name]
|
||||
if !ok {
|
||||
// Changed if an entry is not found.
|
||||
return true
|
||||
}
|
||||
if oldImageRef != imageRef {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// notify emits notifications and events based on the state of the object and
|
||||
// the given PushResult. It tries to always send the PushResult commit message
|
||||
// if there has been any update. Otherwise, a generic up-to-date message. In
|
||||
// case of any failure, the failure message is read from the Ready condition and
|
||||
// included in the event.
|
||||
func (r *ImageUpdateAutomationReconciler) notify(ctx context.Context, oldObj, newObj conditions.Setter, result *source.PushResult, syncNeeded bool) {
|
||||
// Use the Ready message as the notification message by default.
|
||||
ready := conditions.Get(newObj, meta.ReadyCondition)
|
||||
msg := ready.Message
|
||||
|
||||
// If there's a PushResult, use the summary as the notification message.
|
||||
if result != nil {
|
||||
msg = result.Summary()
|
||||
}
|
||||
|
||||
// Was ready before and is ready now, with new push result,
|
||||
if conditions.IsReady(oldObj) && conditions.IsReady(newObj) && result != nil {
|
||||
eventLogf(ctx, r.EventRecorder, newObj, corev1.EventTypeNormal, ready.Reason, "%s", msg)
|
||||
return
|
||||
}
|
||||
|
||||
// Emit events when reconciliation fails or recovers from failure.
|
||||
|
||||
// Became ready from not ready.
|
||||
if !conditions.IsReady(oldObj) && conditions.IsReady(newObj) {
|
||||
eventLogf(ctx, r.EventRecorder, newObj, corev1.EventTypeNormal, ready.Reason, "%s", msg)
|
||||
return
|
||||
}
|
||||
// Not ready, failed. Use the failure message from ready condition.
|
||||
if !conditions.IsReady(newObj) {
|
||||
eventLogf(ctx, r.EventRecorder, newObj, corev1.EventTypeWarning, ready.Reason, "%s", ready.Message)
|
||||
return
|
||||
}
|
||||
|
||||
// No change.
|
||||
|
||||
if !syncNeeded {
|
||||
// Full reconciliation skipped.
|
||||
msg = "no change since last reconciliation"
|
||||
}
|
||||
eventLogf(ctx, r.EventRecorder, newObj, eventv1.EventTypeTrace, meta.SucceededReason, "%s", msg)
|
||||
}
|
||||
|
||||
// eventLogf records events, and logs at the same time.
|
||||
//
|
||||
// This log is different from the debug log in the EventRecorder, in the sense
|
||||
// that this is a simple log. While the debug log contains complete details
|
||||
// about the event.
|
||||
func eventLogf(ctx context.Context, r kuberecorder.EventRecorder, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(messageFmt, args...)
|
||||
// Log and emit event.
|
||||
if eventType == corev1.EventTypeWarning {
|
||||
ctrl.LoggerFrom(ctx).Error(errors.New(reason), msg)
|
||||
} else {
|
||||
ctrl.LoggerFrom(ctx).Info(msg)
|
||||
}
|
||||
r.Eventf(obj, eventType, reason, msg)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
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 controller
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
|
||||
reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
// latestImageChangePredicate implements a predicate for latest image change.
|
||||
// This can be used to filter events from ImagePolicies for change in the latest
|
||||
// image.
|
||||
type latestImageChangePredicate struct {
|
||||
predicate.Funcs
|
||||
}
|
||||
|
||||
func (latestImageChangePredicate) Create(e event.CreateEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (latestImageChangePredicate) Delete(e event.DeleteEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (latestImageChangePredicate) Update(e event.UpdateEvent) bool {
|
||||
if e.ObjectOld == nil || e.ObjectNew == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
oldSource, ok := e.ObjectOld.(*reflectorv1.ImagePolicy)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
newSource, ok := e.ObjectNew.(*reflectorv1.ImagePolicy)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if newSource.Status.LatestRef == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if oldSource.Status.LatestRef == nil || *oldSource.Status.LatestRef != *newSource.Status.LatestRef {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// sourceConfigChangePredicate implements a predicate for source configuration
|
||||
// change. This can be used to filter events from source objects for change in
|
||||
// source configuration.
|
||||
type sourceConfigChangePredicate struct {
|
||||
predicate.Funcs
|
||||
}
|
||||
|
||||
func (sourceConfigChangePredicate) Create(e event.CreateEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (sourceConfigChangePredicate) Delete(e event.DeleteEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (sourceConfigChangePredicate) Update(e event.UpdateEvent) bool {
|
||||
if e.ObjectOld == nil || e.ObjectNew == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration()
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
/*
|
||||
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 controller
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
|
||||
reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
)
|
||||
|
||||
func Test_latestImageChangePredicate_Update(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
beforeFunc func(oldObj, newObj *reflectorv1.ImagePolicy)
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "no latest image",
|
||||
beforeFunc: func(oldObj, newObj *reflectorv1.ImagePolicy) {
|
||||
oldObj.Status.LatestRef = nil
|
||||
newObj.Status.LatestRef = nil
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new image, no old image",
|
||||
beforeFunc: func(oldObj, newObj *reflectorv1.ImagePolicy) {
|
||||
oldObj.Status.LatestRef = nil
|
||||
newObj.Status.LatestRef = &reflectorv1.ImageRef{Name: "foo"}
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "different old and new image",
|
||||
beforeFunc: func(oldObj, newObj *reflectorv1.ImagePolicy) {
|
||||
oldObj.Status.LatestRef = &reflectorv1.ImageRef{Name: "bar"}
|
||||
newObj.Status.LatestRef = &reflectorv1.ImageRef{Name: "foo"}
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
oldObj := &reflectorv1.ImagePolicy{}
|
||||
newObj := oldObj.DeepCopy()
|
||||
if tt.beforeFunc != nil {
|
||||
tt.beforeFunc(oldObj, newObj)
|
||||
}
|
||||
e := event.UpdateEvent{
|
||||
ObjectOld: oldObj,
|
||||
ObjectNew: newObj,
|
||||
}
|
||||
p := latestImageChangePredicate{}
|
||||
g.Expect(p.Update(e)).To(Equal(tt.want))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sourceConfigChangePredicate_Update(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
beforeFunc func(oldObj, newObj *sourcev1.GitRepository)
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "no generation change, same config",
|
||||
beforeFunc: func(oldObj, newObj *sourcev1.GitRepository) {
|
||||
oldObj.Generation = 0
|
||||
newObj.Generation = 0
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new generation, config change",
|
||||
beforeFunc: func(oldObj, newObj *sourcev1.GitRepository) {
|
||||
oldObj.Generation = 1
|
||||
newObj.Generation = 2
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
oldObj := &sourcev1.GitRepository{}
|
||||
newObj := oldObj.DeepCopy()
|
||||
if tt.beforeFunc != nil {
|
||||
tt.beforeFunc(oldObj, newObj)
|
||||
}
|
||||
e := event.UpdateEvent{
|
||||
ObjectOld: oldObj,
|
||||
ObjectNew: newObj,
|
||||
}
|
||||
p := sourceConfigChangePredicate{}
|
||||
g.Expect(p.Update(e)).To(Equal(tt.want))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -1,115 +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 controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
"github.com/fluxcd/pkg/runtime/controller"
|
||||
"github.com/fluxcd/pkg/runtime/testenv"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
// These tests make use of plain Go using Gomega for assertions.
|
||||
// At the beginning of every (sub)test Gomega can be initialized
|
||||
// using gomega.NewWithT.
|
||||
// Refer to http://onsi.github.io/gomega/ to learn more about
|
||||
// Gomega.
|
||||
|
||||
var (
|
||||
k8sClient client.Client
|
||||
testEnv *testenv.Environment
|
||||
ctx = ctrl.SetupSignalHandler()
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
utilruntime.Must(reflectorv1.AddToScheme(scheme.Scheme))
|
||||
utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme))
|
||||
utilruntime.Must(imagev1.AddToScheme(scheme.Scheme))
|
||||
|
||||
code := runTestsWithFeatures(m, nil)
|
||||
if code != 0 {
|
||||
fmt.Println("failed with default feature values")
|
||||
}
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func runTestsWithFeatures(m *testing.M, feats map[string]bool) int {
|
||||
testEnv = testenv.New(
|
||||
testenv.WithCRDPath(
|
||||
filepath.Join("..", "..", "config", "crd", "bases"),
|
||||
filepath.Join("testdata", "crds"),
|
||||
),
|
||||
testenv.WithMaxConcurrentReconciles(2),
|
||||
)
|
||||
|
||||
var err error
|
||||
// Initialize a cacheless client for tests that need the latest objects.
|
||||
k8sClient, err = client.New(testEnv.Config, client.Options{Scheme: scheme.Scheme})
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create k8s client: %v", err))
|
||||
}
|
||||
|
||||
controllerName := "image-automation-controller"
|
||||
if err := (&ImageUpdateAutomationReconciler{
|
||||
Client: testEnv,
|
||||
EventRecorder: record.NewFakeRecorder(32),
|
||||
features: feats,
|
||||
ControllerName: controllerName,
|
||||
}).SetupWithManager(ctx, testEnv, ImageUpdateAutomationReconcilerOptions{
|
||||
RateLimiter: controller.GetDefaultRateLimiter(),
|
||||
}); err != nil {
|
||||
panic(fmt.Sprintf("failed to start ImageUpdateAutomationReconciler: %v", err))
|
||||
}
|
||||
|
||||
go func() {
|
||||
fmt.Println("Starting the test environment")
|
||||
if err := testEnv.Start(ctx); err != nil {
|
||||
panic(fmt.Sprintf("failed to start the test environment manager: %v", err))
|
||||
}
|
||||
}()
|
||||
<-testEnv.Manager.Elected()
|
||||
|
||||
code := m.Run()
|
||||
|
||||
fmt.Println("Stopping the test environment")
|
||||
if err := testEnv.Stop(); err != nil {
|
||||
panic(fmt.Sprintf("failed to stop the test environment: %v", err))
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
Copyright 2022 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 features sets the feature gates that
|
||||
// image-automation-controller supports, and their default
|
||||
// states.
|
||||
package features
|
||||
|
||||
import (
|
||||
"github.com/fluxcd/pkg/auth"
|
||||
feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||
)
|
||||
|
||||
const (
|
||||
// GitForcePushBranch enables the use of "force push" when push branches
|
||||
// are configured.
|
||||
GitForcePushBranch = "GitForcePushBranch"
|
||||
// GitShallowClone enables the use of shallow clones when pulling source from
|
||||
// Git repositories.
|
||||
GitShallowClone = "GitShallowClone"
|
||||
// GitAllBranchReferences enables the download of all branch head references
|
||||
// when push branches are configured. When enabled fixes fluxcd/flux2#3384.
|
||||
GitAllBranchReferences = "GitAllBranchReferences"
|
||||
// GitSparseCheckout enables the use of sparse checkout when pulling source from
|
||||
// Git repositories.
|
||||
GitSparseCheckout = "GitSparseCheckout"
|
||||
// CacheSecretsAndConfigMaps controls whether Secrets and ConfigMaps should
|
||||
// be cached.
|
||||
//
|
||||
// When enabled, it will cache both object types, resulting in increased
|
||||
// memory usage and cluster-wide RBAC permissions (list and watch).
|
||||
CacheSecretsAndConfigMaps = "CacheSecretsAndConfigMaps"
|
||||
)
|
||||
|
||||
var features = map[string]bool{
|
||||
// GitForcePushBranch
|
||||
// opt-out from v0.27
|
||||
GitForcePushBranch: true,
|
||||
|
||||
// GitShallowClone
|
||||
// opt-out from v0.28
|
||||
GitShallowClone: true,
|
||||
|
||||
// GitAllBranchReferences
|
||||
// opt-out from v0.28
|
||||
GitAllBranchReferences: true,
|
||||
|
||||
// GitSparseCheckout
|
||||
// opt-in from v0.42
|
||||
GitSparseCheckout: false,
|
||||
|
||||
// CacheSecretsAndConfigMaps
|
||||
// opt-in from v0.29
|
||||
CacheSecretsAndConfigMaps: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
auth.SetFeatureGates(features)
|
||||
}
|
||||
|
||||
// FeatureGates contains a list of all supported feature gates and
|
||||
// their default values.
|
||||
func FeatureGates() map[string]bool {
|
||||
return features
|
||||
}
|
||||
|
||||
// Enabled verifies whether the feature is enabled or not.
|
||||
//
|
||||
// This is only a wrapper around the Enabled func in
|
||||
// pkg/runtime/features, so callers won't need to import
|
||||
// both packages for checking whether a feature is enabled.
|
||||
func Enabled(feature string) (bool, error) {
|
||||
return feathelper.Enabled(feature)
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
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 policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/fluxcd/pkg/runtime/logger"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/update"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoUpdateStrategy is an update error when the update strategy is not
|
||||
// specified.
|
||||
ErrNoUpdateStrategy = errors.New("no update strategy")
|
||||
// ErrUnsupportedUpdateStrategy is an update error when the provided update
|
||||
// strategy is not supported.
|
||||
ErrUnsupportedUpdateStrategy = errors.New("unsupported update strategy")
|
||||
)
|
||||
|
||||
// ApplyPolicies applies the given set of policies on the source present in the
|
||||
// workDir based on the provided ImageUpdateAutomation configuration.
|
||||
func ApplyPolicies(ctx context.Context, workDir string, obj *imagev1.ImageUpdateAutomation, policies []reflectorv1.ImagePolicy) (update.Result, error) {
|
||||
var result update.Result
|
||||
if obj.Spec.Update == nil {
|
||||
return result, ErrNoUpdateStrategy
|
||||
}
|
||||
if obj.Spec.Update.Strategy != imagev1.UpdateStrategySetters {
|
||||
return result, fmt.Errorf("%w: %s", ErrUnsupportedUpdateStrategy, obj.Spec.Update.Strategy)
|
||||
}
|
||||
|
||||
// Resolve the path to the manifests to apply policies on.
|
||||
manifestPath := workDir
|
||||
if obj.Spec.Update.Path != "" {
|
||||
p, err := securejoin.SecureJoin(workDir, obj.Spec.Update.Path)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to secure join manifest path: %w", err)
|
||||
}
|
||||
manifestPath = p
|
||||
}
|
||||
|
||||
tracelog := log.FromContext(ctx).V(logger.TraceLevel)
|
||||
return update.UpdateWithSetters(tracelog, manifestPath, manifestPath, policies)
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
/*
|
||||
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 policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/otiai10/copy"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/testutil"
|
||||
)
|
||||
|
||||
func testdataPath(path string) string {
|
||||
return filepath.Join("testdata", path)
|
||||
}
|
||||
|
||||
func Test_applyPolicies(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
updateStrategy *imagev1.UpdateStrategy
|
||||
policyLatestImages map[string]string
|
||||
targetPolicyName string
|
||||
replaceMarkerFunc func(g *WithT, path string, policyKey types.NamespacedName)
|
||||
inputPath string
|
||||
expectedPath string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid update strategy and one policy",
|
||||
updateStrategy: &imagev1.UpdateStrategy{
|
||||
Strategy: imagev1.UpdateStrategySetters,
|
||||
},
|
||||
policyLatestImages: map[string]string{
|
||||
"policy1": "helloworld:1.0.1",
|
||||
},
|
||||
targetPolicyName: "policy1",
|
||||
inputPath: testdataPath("appconfig"),
|
||||
expectedPath: testdataPath("appconfig-setters-expected"),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no update strategy",
|
||||
updateStrategy: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unknown update strategy",
|
||||
updateStrategy: &imagev1.UpdateStrategy{
|
||||
Strategy: "foo",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid update strategy and multiple policies",
|
||||
updateStrategy: &imagev1.UpdateStrategy{
|
||||
Strategy: imagev1.UpdateStrategySetters,
|
||||
},
|
||||
policyLatestImages: map[string]string{
|
||||
"policy1": "foo:1.1.1",
|
||||
"policy2": "helloworld:1.0.1",
|
||||
"policy3": "bar:2.2.2",
|
||||
},
|
||||
targetPolicyName: "policy2",
|
||||
inputPath: testdataPath("appconfig"),
|
||||
expectedPath: testdataPath("appconfig-setters-expected"),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid update strategy with update path",
|
||||
updateStrategy: &imagev1.UpdateStrategy{
|
||||
Strategy: imagev1.UpdateStrategySetters,
|
||||
Path: "./yes",
|
||||
},
|
||||
policyLatestImages: map[string]string{
|
||||
"policy1": "helloworld:1.0.1",
|
||||
},
|
||||
targetPolicyName: "policy1",
|
||||
replaceMarkerFunc: func(g *WithT, path string, policyKey types.NamespacedName) {
|
||||
g.Expect(testutil.ReplaceMarker(filepath.Join(path, "yes", "deploy.yaml"), policyKey)).ToNot(HaveOccurred())
|
||||
g.Expect(testutil.ReplaceMarker(filepath.Join(path, "no", "deploy.yaml"), policyKey)).ToNot(HaveOccurred())
|
||||
},
|
||||
inputPath: testdataPath("pathconfig"),
|
||||
expectedPath: testdataPath("pathconfig-expected"),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
testNS := "test-ns"
|
||||
workDir := t.TempDir()
|
||||
|
||||
// Create all the policy objects.
|
||||
policyList := []reflectorv1.ImagePolicy{}
|
||||
for name, image := range tt.policyLatestImages {
|
||||
policy := &reflectorv1.ImagePolicy{}
|
||||
policy.Name = name
|
||||
policy.Namespace = testNS
|
||||
policy.Status = reflectorv1.ImagePolicyStatus{
|
||||
LatestRef: testutil.ImageToRef(image),
|
||||
}
|
||||
policyList = append(policyList, *policy)
|
||||
}
|
||||
targetPolicyKey := types.NamespacedName{
|
||||
Name: tt.targetPolicyName, Namespace: testNS,
|
||||
}
|
||||
|
||||
if tt.inputPath != "" {
|
||||
g.Expect(copy.Copy(tt.inputPath, workDir)).ToNot(HaveOccurred())
|
||||
// Update the test files with the target policy.
|
||||
if tt.replaceMarkerFunc != nil {
|
||||
tt.replaceMarkerFunc(g, workDir, targetPolicyKey)
|
||||
} else {
|
||||
g.Expect(testutil.ReplaceMarker(filepath.Join(workDir, "deploy.yaml"), targetPolicyKey)).ToNot(HaveOccurred())
|
||||
}
|
||||
}
|
||||
|
||||
updateAuto := &imagev1.ImageUpdateAutomation{}
|
||||
updateAuto.Name = "test-update"
|
||||
updateAuto.Namespace = testNS
|
||||
updateAuto.Spec = imagev1.ImageUpdateAutomationSpec{
|
||||
Update: tt.updateStrategy,
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
reflectorv1.AddToScheme(scheme)
|
||||
imagev1.AddToScheme(scheme)
|
||||
|
||||
_, err := ApplyPolicies(context.TODO(), workDir, updateAuto, policyList)
|
||||
g.Expect(err != nil).To(Equal(tt.wantErr))
|
||||
|
||||
// Check the results if there wasn't any error.
|
||||
if !tt.wantErr {
|
||||
expected := t.TempDir()
|
||||
copy.Copy(tt.expectedPath, expected)
|
||||
// Update the markers in the expected test data.
|
||||
if tt.replaceMarkerFunc != nil {
|
||||
tt.replaceMarkerFunc(g, expected, targetPolicyKey)
|
||||
} else {
|
||||
g.Expect(testutil.ReplaceMarker(filepath.Join(expected, "deploy.yaml"), targetPolicyKey)).ToNot(HaveOccurred())
|
||||
}
|
||||
testutil.ExpectMatchingDirectories(g, workDir, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-no
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -1,10 +0,0 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-yes
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.1 # SETTER_SITE
|
|
@ -1,10 +0,0 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-no
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -1,10 +0,0 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-yes
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -1,339 +0,0 @@
|
|||
/*
|
||||
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 source
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/fluxcd/pkg/runtime/secrets"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/pkg/auth"
|
||||
authutils "github.com/fluxcd/pkg/auth/utils"
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/github"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
const (
|
||||
signingSecretKey = "git.asc"
|
||||
signingPassphraseKey = "passphrase"
|
||||
)
|
||||
|
||||
// gitSrcCfg contains all the Git configurations related to a source derived
|
||||
// from the given configurations and the environment.
|
||||
type gitSrcCfg struct {
|
||||
srcKey types.NamespacedName
|
||||
url string
|
||||
pushBranch string
|
||||
switchBranch bool
|
||||
timeout *metav1.Duration
|
||||
checkoutRef *sourcev1.GitRepositoryRef
|
||||
authOpts *git.AuthOptions
|
||||
clientOpts []gogit.ClientOption
|
||||
signingEntity *openpgp.Entity
|
||||
}
|
||||
|
||||
func buildGitConfig(ctx context.Context, c client.Client, originKey, srcKey types.NamespacedName, gitSpec *imagev1.GitSpec, opts SourceOptions) (*gitSrcCfg, error) {
|
||||
var err error
|
||||
cfg := &gitSrcCfg{
|
||||
srcKey: srcKey,
|
||||
}
|
||||
|
||||
// Get the repo.
|
||||
repo := &sourcev1.GitRepository{}
|
||||
if err = c.Get(ctx, srcKey, repo); err != nil {
|
||||
if client.IgnoreNotFound(err) == nil {
|
||||
return nil, fmt.Errorf("referenced git repository does not exist: %w", err)
|
||||
}
|
||||
}
|
||||
cfg.url = repo.Spec.URL
|
||||
|
||||
// Configure Git operation timeout from the GitRepository configuration.
|
||||
if repo.Spec.Timeout != nil {
|
||||
cfg.timeout = repo.Spec.Timeout
|
||||
} else {
|
||||
cfg.timeout = &metav1.Duration{Duration: time.Minute}
|
||||
}
|
||||
|
||||
// Get the checkout ref for the source, prioritizing the image automation
|
||||
// object gitSpec checkout reference and falling back to the GitRepository
|
||||
// reference if not provided.
|
||||
// var checkoutRef *sourcev1.GitRepositoryRef
|
||||
if gitSpec.Checkout != nil {
|
||||
cfg.checkoutRef = &gitSpec.Checkout.Reference
|
||||
} else if repo.Spec.Reference != nil {
|
||||
cfg.checkoutRef = repo.Spec.Reference
|
||||
} // else remain as `nil` and git.DefaultBranch will be used.
|
||||
|
||||
// Configure push first as the client options below depend on the push
|
||||
// configuration.
|
||||
if err = configurePush(cfg, gitSpec, cfg.checkoutRef); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var proxyURL *url.URL
|
||||
var proxyOpts *transport.ProxyOptions
|
||||
// Check if a proxy secret reference is provided in the GitRepository spec.
|
||||
if repo.Spec.ProxySecretRef != nil {
|
||||
secretRef := types.NamespacedName{
|
||||
Name: repo.Spec.ProxySecretRef.Name,
|
||||
Namespace: repo.GetNamespace(),
|
||||
}
|
||||
// Get the proxy URL from runtime/secret
|
||||
proxyURL, err = secrets.ProxyURLFromSecretRef(ctx, c, secretRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxyOpts = &transport.ProxyOptions{URL: proxyURL.String()}
|
||||
}
|
||||
|
||||
cfg.authOpts, err = getAuthOpts(ctx, c, repo, opts, proxyURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.clientOpts = []gogit.ClientOption{gogit.WithDiskStorage()}
|
||||
if cfg.authOpts.Transport == git.HTTP {
|
||||
cfg.clientOpts = append(cfg.clientOpts, gogit.WithInsecureCredentialsOverHTTP())
|
||||
}
|
||||
if proxyOpts != nil {
|
||||
cfg.clientOpts = append(cfg.clientOpts, gogit.WithProxy(*proxyOpts))
|
||||
}
|
||||
// If the push branch is different from the checkout ref, we need to
|
||||
// have all the references downloaded at clone time, to ensure that
|
||||
// SwitchBranch will have access to the target branch state. fluxcd/flux2#3384
|
||||
//
|
||||
// To always overwrite the push branch, the feature gate
|
||||
// GitAllBranchReferences can be set to false, which will cause
|
||||
// the SwitchBranch operation to ignore the remote branch state.
|
||||
if cfg.switchBranch {
|
||||
cfg.clientOpts = append(cfg.clientOpts, gogit.WithSingleBranch(!opts.gitAllBranchReferences))
|
||||
}
|
||||
|
||||
if gitSpec.Commit.SigningKey != nil {
|
||||
if cfg.signingEntity, err = getSigningEntity(ctx, c, originKey.Namespace, gitSpec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func configurePush(cfg *gitSrcCfg, gitSpec *imagev1.GitSpec, checkoutRef *sourcev1.GitRepositoryRef) error {
|
||||
if gitSpec.Push != nil && gitSpec.Push.Branch != "" {
|
||||
cfg.pushBranch = gitSpec.Push.Branch
|
||||
|
||||
if checkoutRef != nil {
|
||||
if cfg.pushBranch != checkoutRef.Branch {
|
||||
cfg.switchBranch = true
|
||||
}
|
||||
} else {
|
||||
// Compare with the git default branch when no checkout ref is
|
||||
// explicitly defined.
|
||||
if cfg.pushBranch != git.DefaultBranch {
|
||||
cfg.switchBranch = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If no push branch is configured above, use the branch from checkoutRef.
|
||||
|
||||
// Here's where it gets constrained. If there's no push branch
|
||||
// given, then the checkout ref must include a branch, and
|
||||
// that can be used.
|
||||
if checkoutRef == nil || checkoutRef.Branch == "" {
|
||||
return errors.New("push spec not provided, and cannot be inferred from .spec.git.checkout.ref or GitRepository .spec.ref")
|
||||
}
|
||||
cfg.pushBranch = checkoutRef.Branch
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAuthOpts(ctx context.Context, c client.Client, repo *sourcev1.GitRepository,
|
||||
srcOpts SourceOptions, proxyURL *url.URL) (*git.AuthOptions, error) {
|
||||
var secret *corev1.Secret
|
||||
var data map[string][]byte
|
||||
var err error
|
||||
if repo.Spec.SecretRef != nil {
|
||||
secret, err = getSecret(ctx, c, repo.Spec.SecretRef.Name, repo.GetNamespace())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get auth secret '%s/%s': %w", repo.GetNamespace(), repo.Spec.SecretRef.Name, err)
|
||||
}
|
||||
data = secret.Data
|
||||
}
|
||||
|
||||
u, err := url.Parse(repo.Spec.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse URL '%s': %w", repo.Spec.URL, err)
|
||||
}
|
||||
|
||||
opts, err := git.NewAuthOptions(*u, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to configure authentication options: %w", err)
|
||||
}
|
||||
|
||||
var getCreds func() (*authutils.GitCredentials, error)
|
||||
switch provider := repo.GetProvider(); provider {
|
||||
case sourcev1.GitProviderAzure: // If AWS or GCP are added in the future they can be added here separated by a comma.
|
||||
getCreds = func() (*authutils.GitCredentials, error) {
|
||||
opts := []auth.Option{
|
||||
auth.WithClient(c),
|
||||
auth.WithServiceAccountNamespace(srcOpts.objNamespace),
|
||||
}
|
||||
|
||||
if srcOpts.tokenCache != nil {
|
||||
involvedObject := cache.InvolvedObject{
|
||||
Kind: imagev1.ImageUpdateAutomationKind,
|
||||
Name: srcOpts.objName,
|
||||
Namespace: srcOpts.objNamespace,
|
||||
Operation: cache.OperationReconcile,
|
||||
}
|
||||
opts = append(opts, auth.WithCache(*srcOpts.tokenCache, involvedObject))
|
||||
}
|
||||
|
||||
if proxyURL != nil {
|
||||
opts = append(opts, auth.WithProxyURL(*proxyURL))
|
||||
}
|
||||
|
||||
return authutils.GetGitCredentials(ctx, provider, opts...)
|
||||
}
|
||||
case sourcev1.GitProviderGitHub:
|
||||
// if provider is github, but secret ref is not specified
|
||||
if repo.Spec.SecretRef == nil {
|
||||
return nil, fmt.Errorf("secretRef with github app data must be specified when provider is set to github: %w", ErrInvalidSourceConfiguration)
|
||||
}
|
||||
authMethods, err := secrets.AuthMethodsFromSecret(ctx, secret, secrets.WithTLSSystemCertPool())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !authMethods.HasGitHubAppData() {
|
||||
return nil, fmt.Errorf("secretRef with github app data must be specified when provider is set to github: %w", ErrInvalidSourceConfiguration)
|
||||
}
|
||||
|
||||
getCreds = func() (*authutils.GitCredentials, error) {
|
||||
var appOpts []github.OptFunc
|
||||
|
||||
appOpts = append(appOpts, github.WithAppData(authMethods.GitHubAppData))
|
||||
|
||||
if proxyURL != nil {
|
||||
appOpts = append(appOpts, github.WithProxyURL(proxyURL))
|
||||
}
|
||||
|
||||
if srcOpts.tokenCache != nil {
|
||||
appOpts = append(appOpts, github.WithCache(srcOpts.tokenCache, imagev1.ImageUpdateAutomationKind,
|
||||
srcOpts.objName, srcOpts.objNamespace, cache.OperationReconcile))
|
||||
}
|
||||
|
||||
if authMethods.HasTLS() {
|
||||
appOpts = append(appOpts, github.WithTLSConfig(authMethods.TLS))
|
||||
}
|
||||
|
||||
username, password, err := github.GetCredentials(ctx, appOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &authutils.GitCredentials{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}, nil
|
||||
}
|
||||
default:
|
||||
// analyze secret, if it has github app data, perhaps provider should have been github.
|
||||
if appID := data[github.KeyAppID]; len(appID) != 0 {
|
||||
return nil, fmt.Errorf("secretRef '%s/%s' has github app data but provider is not set to github: %w", repo.GetNamespace(), repo.Spec.SecretRef.Name, ErrInvalidSourceConfiguration)
|
||||
}
|
||||
}
|
||||
if getCreds != nil {
|
||||
creds, err := getCreds()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to configure authentication options: %w", err)
|
||||
}
|
||||
opts.BearerToken = creds.BearerToken
|
||||
opts.Username = creds.Username
|
||||
opts.Password = creds.Password
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func getSigningEntity(ctx context.Context, c client.Client, namespace string, gitSpec *imagev1.GitSpec) (*openpgp.Entity, error) {
|
||||
secretName := gitSpec.Commit.SigningKey.SecretRef.Name
|
||||
secretData, err := getSecretData(ctx, c, secretName, namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not find signing key secret '%s': %w", secretName, err)
|
||||
}
|
||||
|
||||
data, ok := secretData[signingSecretKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("signing key secret '%s' does not contain a 'git.asc' key", secretName)
|
||||
}
|
||||
|
||||
// Read entity from secret value
|
||||
entities, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read signing key from secret '%s': %w", secretName, err)
|
||||
}
|
||||
if len(entities) > 1 {
|
||||
return nil, fmt.Errorf("multiple entities read from secret '%s', could not determine which signing key to use", secretName)
|
||||
}
|
||||
|
||||
entity := entities[0]
|
||||
if entity.PrivateKey != nil && entity.PrivateKey.Encrypted {
|
||||
passphrase, ok := secretData[signingPassphraseKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can not use passphrase protected signing key without '%s' field present in secret %s",
|
||||
"passphrase", secretName)
|
||||
}
|
||||
if err = entity.PrivateKey.Decrypt([]byte(passphrase)); err != nil {
|
||||
return nil, fmt.Errorf("could not decrypt private key of the signing key present in secret %s: %w", secretName, err)
|
||||
}
|
||||
}
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
func getSecretData(ctx context.Context, c client.Client, name, namespace string) (map[string][]byte, error) {
|
||||
secret, err := getSecret(ctx, c, name, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secret.Data, nil
|
||||
}
|
||||
|
||||
func getSecret(ctx context.Context, c client.Client, name, namespace string) (*corev1.Secret, error) {
|
||||
key := types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
}
|
||||
secret := &corev1.Secret{}
|
||||
if err := c.Get(ctx, key, secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secret, nil
|
||||
}
|
|
@ -1,616 +0,0 @@
|
|||
/*
|
||||
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 source
|
||||
|
||||
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"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/github"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/testutil"
|
||||
)
|
||||
|
||||
func Test_getAuthOpts(t *testing.T) {
|
||||
namespace := "default"
|
||||
|
||||
invalidAuthSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "invalid-auth",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"password": []byte("pass"),
|
||||
},
|
||||
}
|
||||
|
||||
validAuthSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-auth",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"username": []byte("user"),
|
||||
"password": []byte("pass"),
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
secretName string
|
||||
want *git.AuthOptions
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "non-existing secret",
|
||||
secretName: "non-existing",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid secret",
|
||||
url: "https://example.com",
|
||||
secretName: "invalid-auth",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid secret",
|
||||
url: "https://example.com",
|
||||
secretName: "valid-auth",
|
||||
want: &git.AuthOptions{
|
||||
Transport: git.HTTPS,
|
||||
Host: "example.com",
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no secret",
|
||||
url: "https://example.com",
|
||||
want: &git.AuthOptions{
|
||||
Transport: git.HTTPS,
|
||||
Host: "example.com",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid URL",
|
||||
url: "://example.com",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
clientBuilder := fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme.Scheme).
|
||||
WithObjects(invalidAuthSecret, validAuthSecret)
|
||||
c := clientBuilder.Build()
|
||||
|
||||
gitRepo := &sourcev1.GitRepository{}
|
||||
gitRepo.Namespace = namespace
|
||||
gitRepo.Spec = sourcev1.GitRepositorySpec{
|
||||
URL: tt.url,
|
||||
}
|
||||
if tt.secretName != "" {
|
||||
gitRepo.Spec.SecretRef = &meta.LocalObjectReference{Name: tt.secretName}
|
||||
}
|
||||
|
||||
got, err := getAuthOpts(context.TODO(), c, gitRepo, SourceOptions{}, nil)
|
||||
if (err != nil) != tt.wantErr {
|
||||
g.Fail(fmt.Sprintf("unexpected error: %v", err))
|
||||
return
|
||||
}
|
||||
g.Expect(got).To(Equal(tt.want))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getAuthOpts_providerAuth(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
secret *corev1.Secret
|
||||
beforeFunc func(obj *sourcev1.GitRepository)
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "azure provider",
|
||||
url: "https://dev.azure.com/foo/bar/_git/baz",
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderAzure
|
||||
},
|
||||
wantErr: "ManagedIdentityCredential",
|
||||
},
|
||||
{
|
||||
name: "github provider with no secret ref",
|
||||
url: "https://github.com/org/repo.git",
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderGitHub
|
||||
},
|
||||
wantErr: "secretRef with github app data must be specified when provider is set to github: invalid source configuration",
|
||||
},
|
||||
{
|
||||
name: "github provider with secret ref that does not exist",
|
||||
url: "https://github.com/org/repo.git",
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderGitHub
|
||||
obj.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: "githubAppSecret",
|
||||
}
|
||||
},
|
||||
wantErr: "failed to get auth secret '/githubAppSecret': secrets \"githubAppSecret\" not found",
|
||||
},
|
||||
{
|
||||
name: "github provider with github app data in secret",
|
||||
url: "https://example.com/org/repo",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "githubAppSecret",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
github.KeyAppID: []byte("123"),
|
||||
github.KeyAppInstallationID: []byte("456"),
|
||||
github.KeyAppPrivateKey: []byte("abc"),
|
||||
},
|
||||
},
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderGitHub
|
||||
obj.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: "githubAppSecret",
|
||||
}
|
||||
},
|
||||
wantErr: "Key must be a PEM encoded PKCS1 or PKCS8 key",
|
||||
},
|
||||
{
|
||||
name: "github provider with basic auth in secret",
|
||||
url: "https://example.com/org/repo",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "basic-auth-secret",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"username": []byte("abc"),
|
||||
"password": []byte(""),
|
||||
},
|
||||
},
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderGitHub
|
||||
obj.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: "basic-auth-secret",
|
||||
}
|
||||
},
|
||||
wantErr: "secretRef with github app data must be specified when provider is set to github",
|
||||
},
|
||||
{
|
||||
name: "generic provider with github app data in secret",
|
||||
url: "https://example.com/org/repo",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "githubAppSecret",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
github.KeyAppID: []byte("123"),
|
||||
},
|
||||
},
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderGeneric
|
||||
obj.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: "githubAppSecret",
|
||||
}
|
||||
},
|
||||
wantErr: "secretRef '/githubAppSecret' has github app data but provider is not set to github: invalid source configuration",
|
||||
},
|
||||
{
|
||||
name: "generic provider",
|
||||
url: "https://example.com/org/repo",
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderGeneric
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no provider",
|
||||
url: "https://example.com/org/repo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
clientBuilder := fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme.Scheme).
|
||||
WithStatusSubresource(&sourcev1.GitRepository{})
|
||||
|
||||
if tt.secret != nil {
|
||||
clientBuilder.WithObjects(tt.secret)
|
||||
}
|
||||
c := clientBuilder.Build()
|
||||
obj := &sourcev1.GitRepository{
|
||||
Spec: sourcev1.GitRepositorySpec{
|
||||
URL: tt.url,
|
||||
},
|
||||
}
|
||||
|
||||
if tt.beforeFunc != nil {
|
||||
tt.beforeFunc(obj)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
opts, err := getAuthOpts(ctx, c, obj, SourceOptions{}, nil)
|
||||
|
||||
if tt.wantErr != "" {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
||||
} else {
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(opts).ToNot(BeNil())
|
||||
g.Expect(opts.BearerToken).To(BeEmpty())
|
||||
g.Expect(opts.Username).To(BeEmpty())
|
||||
g.Expect(opts.Password).To(BeEmpty())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSigningEntity(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
namespace := "default"
|
||||
|
||||
passphrase := "abcde12345"
|
||||
_, keyEncrypted := testutil.GetSigningKeyPair(g, passphrase)
|
||||
encryptedKeySecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "encrypted-key",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
signingSecretKey: keyEncrypted,
|
||||
signingPassphraseKey: []byte(passphrase),
|
||||
},
|
||||
}
|
||||
|
||||
_, keyUnencrypted := testutil.GetSigningKeyPair(g, "")
|
||||
unencryptedKeySecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "unencrypted-key",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
signingSecretKey: keyUnencrypted,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
secretName string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "non-existing secret",
|
||||
secretName: "non-existing",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unencrypted key",
|
||||
secretName: "unencrypted-key",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "encrypted key",
|
||||
secretName: "encrypted-key",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
clientBuilder := fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme.Scheme).
|
||||
WithObjects(encryptedKeySecret, unencryptedKeySecret)
|
||||
c := clientBuilder.Build()
|
||||
|
||||
gitSpec := &imagev1.GitSpec{}
|
||||
if tt.secretName != "" {
|
||||
gitSpec.Commit = imagev1.CommitSpec{
|
||||
SigningKey: &imagev1.SigningKey{
|
||||
SecretRef: meta.LocalObjectReference{Name: tt.secretName},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
_, err := getSigningEntity(context.TODO(), c, namespace, gitSpec)
|
||||
if (err != nil) != tt.wantErr {
|
||||
g.Fail(fmt.Sprintf("unexpected error: %v", err))
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildGitConfig(t *testing.T) {
|
||||
testGitRepoName := "test-gitrepo"
|
||||
namespace := "foo-ns"
|
||||
testTimeout := &metav1.Duration{Duration: time.Minute}
|
||||
testGitURL := "https://example.com"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
gitSpec *imagev1.GitSpec
|
||||
gitRepoName string
|
||||
gitRepoRef *sourcev1.GitRepositoryRef
|
||||
gitRepoTimeout *metav1.Duration
|
||||
gitRepoURL string
|
||||
gitRepoProxyData map[string][]byte
|
||||
srcOpts SourceOptions
|
||||
wantErr bool
|
||||
wantCheckoutRef *sourcev1.GitRepositoryRef
|
||||
wantPushBranch string
|
||||
wantSwitchBranch bool
|
||||
wantTimeout *metav1.Duration
|
||||
}{
|
||||
{
|
||||
name: "same branch, gitSpec checkoutRef",
|
||||
gitSpec: &imagev1.GitSpec{
|
||||
Checkout: &imagev1.GitCheckoutSpec{
|
||||
Reference: sourcev1.GitRepositoryRef{Branch: "aaa"},
|
||||
},
|
||||
},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "aaa",
|
||||
},
|
||||
wantPushBranch: "aaa",
|
||||
wantSwitchBranch: false,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "different branch, gitSpec checkoutRef",
|
||||
gitSpec: &imagev1.GitSpec{
|
||||
Checkout: &imagev1.GitCheckoutSpec{
|
||||
Reference: sourcev1.GitRepositoryRef{Branch: "aaa"},
|
||||
},
|
||||
Push: &imagev1.PushSpec{
|
||||
Branch: "bbb",
|
||||
},
|
||||
},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "aaa",
|
||||
},
|
||||
wantPushBranch: "bbb",
|
||||
wantSwitchBranch: true,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "same branch, gitrepo checkoutRef",
|
||||
gitSpec: &imagev1.GitSpec{},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantPushBranch: "ccc",
|
||||
wantSwitchBranch: false,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "different branch, gitrepo checkoutRef",
|
||||
gitSpec: &imagev1.GitSpec{
|
||||
Push: &imagev1.PushSpec{
|
||||
Branch: "ddd",
|
||||
},
|
||||
},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantPushBranch: "ddd",
|
||||
wantSwitchBranch: true,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "no checkoutRef defined",
|
||||
gitSpec: &imagev1.GitSpec{
|
||||
Push: &imagev1.PushSpec{
|
||||
Branch: "aaa",
|
||||
},
|
||||
},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
wantErr: false,
|
||||
wantCheckoutRef: nil, // Use the git default checkout branch.
|
||||
wantPushBranch: "aaa",
|
||||
wantSwitchBranch: true,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "gitSpec override gitRepo checkout config",
|
||||
gitSpec: &imagev1.GitSpec{
|
||||
Checkout: &imagev1.GitCheckoutSpec{
|
||||
Reference: sourcev1.GitRepositoryRef{Branch: "aaa"},
|
||||
},
|
||||
Push: &imagev1.PushSpec{
|
||||
Branch: "bbb",
|
||||
},
|
||||
},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "aaa",
|
||||
},
|
||||
wantPushBranch: "bbb",
|
||||
wantSwitchBranch: true,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "non-existing gitRepo",
|
||||
gitSpec: &imagev1.GitSpec{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "use gitrepo timeout",
|
||||
gitSpec: &imagev1.GitSpec{},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
gitRepoTimeout: &metav1.Duration{Duration: 30 * time.Second},
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantPushBranch: "ccc",
|
||||
wantSwitchBranch: false,
|
||||
wantTimeout: &metav1.Duration{Duration: 30 * time.Second},
|
||||
},
|
||||
{
|
||||
name: "bad git URL",
|
||||
gitSpec: &imagev1.GitSpec{},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: "://example.com",
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "proxy config",
|
||||
gitSpec: &imagev1.GitSpec{},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
gitRepoProxyData: map[string][]byte{
|
||||
"address": []byte("http://example.com"),
|
||||
},
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantPushBranch: "ccc",
|
||||
wantSwitchBranch: false,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
testObjects := []client.Object{}
|
||||
|
||||
var proxySecret *corev1.Secret
|
||||
if tt.gitRepoProxyData != nil {
|
||||
proxySecret = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-proxy",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: tt.gitRepoProxyData,
|
||||
}
|
||||
testObjects = append(testObjects, proxySecret)
|
||||
}
|
||||
|
||||
var gitRepo *sourcev1.GitRepository
|
||||
if tt.gitRepoName != "" {
|
||||
gitRepo = &sourcev1.GitRepository{}
|
||||
gitRepo.Name = testGitRepoName
|
||||
gitRepo.Namespace = namespace
|
||||
gitRepo.Spec = sourcev1.GitRepositorySpec{}
|
||||
if tt.gitRepoURL != "" {
|
||||
gitRepo.Spec.URL = tt.gitRepoURL
|
||||
}
|
||||
if tt.gitRepoRef != nil {
|
||||
gitRepo.Spec.Reference = tt.gitRepoRef
|
||||
}
|
||||
if tt.gitRepoTimeout != nil {
|
||||
gitRepo.Spec.Timeout = tt.gitRepoTimeout
|
||||
}
|
||||
if proxySecret != nil {
|
||||
gitRepo.Spec.ProxySecretRef = &meta.LocalObjectReference{Name: proxySecret.Name}
|
||||
}
|
||||
testObjects = append(testObjects, gitRepo)
|
||||
}
|
||||
|
||||
clientBuilder := fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme.Scheme).
|
||||
WithObjects(testObjects...)
|
||||
c := clientBuilder.Build()
|
||||
|
||||
gitRepoKey := types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: tt.gitRepoName,
|
||||
}
|
||||
|
||||
updateAutoKey := types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: "test-update",
|
||||
}
|
||||
|
||||
gitSrcCfg, err := buildGitConfig(context.TODO(), c, updateAutoKey, gitRepoKey, tt.gitSpec, tt.srcOpts)
|
||||
if (err != nil) != tt.wantErr {
|
||||
g.Fail(fmt.Sprintf("unexpected error: %v", err))
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
g.Expect(gitSrcCfg.checkoutRef).To(Equal(tt.wantCheckoutRef), "unexpected checkoutRef")
|
||||
g.Expect(gitSrcCfg.pushBranch).To(Equal(tt.wantPushBranch), "unexpected push branch")
|
||||
g.Expect(gitSrcCfg.switchBranch).To(Equal(tt.wantSwitchBranch), "unexpected switch branch")
|
||||
g.Expect(gitSrcCfg.timeout).To(Equal(tt.wantTimeout), "unexpected git operation timeout")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,483 +0,0 @@
|
|||
/*
|
||||
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 source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
"github.com/fluxcd/pkg/git/repository"
|
||||
"github.com/fluxcd/pkg/runtime/acl"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
"github.com/fluxcd/pkg/runtime/logger"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/update"
|
||||
)
|
||||
|
||||
// ErrInvalidSourceConfiguration is an error for invalid source configuration.
|
||||
var ErrInvalidSourceConfiguration = errors.New("invalid source configuration")
|
||||
|
||||
// RemovedTemplateFieldError represents an error when a removed template field is used.
|
||||
type RemovedTemplateFieldError struct {
|
||||
Field string
|
||||
}
|
||||
|
||||
func (e *RemovedTemplateFieldError) Error() string {
|
||||
switch e.Field {
|
||||
case ".Updated":
|
||||
return "template uses removed '.Updated' field. Please use '.Changed' instead. See: https://fluxcd.io/flux/components/image/imageupdateautomations/#message-template"
|
||||
case ".Changed.ImageResult":
|
||||
return "template uses removed '.Changed.ImageResult' field. Please use '.Changed.FileChanges' or '.Changed.Objects' instead. See: https://fluxcd.io/flux/components/image/imageupdateautomations/#message-template"
|
||||
default:
|
||||
return fmt.Sprintf("template uses removed '%s' field. See: https://fluxcd.io/flux/components/image/imageupdateautomations/#message-template", e.Field)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *RemovedTemplateFieldError) Is(target error) bool {
|
||||
return errors.Is(target, ErrRemovedTemplateField)
|
||||
}
|
||||
|
||||
// ErrRemovedTemplateField is a sentinel error for removed template field usage.
|
||||
var ErrRemovedTemplateField = &RemovedTemplateFieldError{}
|
||||
|
||||
const defaultMessageTemplate = `Update from image update automation`
|
||||
|
||||
// TemplateData is the type of the value given to the commit message
|
||||
// template.
|
||||
type TemplateData struct {
|
||||
AutomationObject types.NamespacedName
|
||||
Changed update.Result
|
||||
Values map[string]string
|
||||
}
|
||||
|
||||
// SourceManager manages source.
|
||||
type SourceManager struct {
|
||||
srcCfg *gitSrcCfg
|
||||
automationObjKey types.NamespacedName
|
||||
gitClient *gogit.Client
|
||||
workingDir string
|
||||
}
|
||||
|
||||
// SourceOptions contains the optional attributes of SourceManager.
|
||||
type SourceOptions struct {
|
||||
noCrossNamespaceRef bool
|
||||
gitAllBranchReferences bool
|
||||
tokenCache *cache.TokenCache
|
||||
objName string
|
||||
objNamespace string
|
||||
}
|
||||
|
||||
// SourceOption configures the SourceManager options.
|
||||
type SourceOption func(*SourceOptions)
|
||||
|
||||
// WithSourceOptionNoCrossNamespaceRef configures the SourceManager to disable
|
||||
// cross namespace references.
|
||||
func WithSourceOptionNoCrossNamespaceRef() SourceOption {
|
||||
return func(so *SourceOptions) {
|
||||
so.noCrossNamespaceRef = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithSourceOptionGitAllBranchReferences configures the SourceManager to fetch
|
||||
// all the Git branch references that are present in the remote repository.
|
||||
func WithSourceOptionGitAllBranchReferences() SourceOption {
|
||||
return func(so *SourceOptions) {
|
||||
so.gitAllBranchReferences = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithSourceOptionTokenCache configures the SourceManager to use the provided
|
||||
// token cache.
|
||||
func WithSourceOptionTokenCache(tc *cache.TokenCache) SourceOption {
|
||||
return func(so *SourceOptions) {
|
||||
so.tokenCache = tc
|
||||
}
|
||||
}
|
||||
|
||||
// WithSourceOptionInvolvedObject configures the SourceManager to use the
|
||||
// provided ImageUpdateAutomation object.
|
||||
func WithSourceOptionInvolvedObject(name, namespace string) SourceOption {
|
||||
return func(so *SourceOptions) {
|
||||
so.objName = name
|
||||
so.objNamespace = namespace
|
||||
}
|
||||
}
|
||||
|
||||
// NewSourceManager takes all the provided inputs, validates them and returns a
|
||||
// SourceManager which can be used to operate on the configured source.
|
||||
func NewSourceManager(ctx context.Context, c client.Client, obj *imagev1.ImageUpdateAutomation, options ...SourceOption) (*SourceManager, error) {
|
||||
opts := &SourceOptions{}
|
||||
for _, o := range options {
|
||||
o(opts)
|
||||
}
|
||||
|
||||
// Only GitRepository source is supported.
|
||||
if obj.Spec.SourceRef.Kind != sourcev1.GitRepositoryKind {
|
||||
return nil, fmt.Errorf("source kind '%s' not supported: %w", obj.Spec.SourceRef.Kind, ErrInvalidSourceConfiguration)
|
||||
}
|
||||
|
||||
if obj.Spec.GitSpec == nil {
|
||||
return nil, fmt.Errorf("source kind '%s' necessitates field .spec.git: %w", sourcev1.GitRepositoryKind, ErrInvalidSourceConfiguration)
|
||||
}
|
||||
|
||||
// Build source reference configuration to fetch and validate it.
|
||||
srcNamespace := obj.GetNamespace()
|
||||
if obj.Spec.SourceRef.Namespace != "" {
|
||||
srcNamespace = obj.Spec.SourceRef.Namespace
|
||||
}
|
||||
|
||||
// srcKey is the GitRepository object key.
|
||||
srcKey := types.NamespacedName{Name: obj.Spec.SourceRef.Name, Namespace: srcNamespace}
|
||||
// originKey is the update automation object key.
|
||||
originKey := client.ObjectKeyFromObject(obj)
|
||||
|
||||
// Check if the source is accessible.
|
||||
if opts.noCrossNamespaceRef && srcKey.Namespace != obj.GetNamespace() {
|
||||
return nil, acl.AccessDeniedError(fmt.Sprintf("can't access '%s/%s', cross-namespace references have been blocked", sourcev1.GitRepositoryKind, srcKey))
|
||||
}
|
||||
|
||||
gitSrcCfg, err := buildGitConfig(ctx, c, originKey, srcKey, obj.Spec.GitSpec, *opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workDir, err := os.MkdirTemp("", fmt.Sprintf("%s-%s", gitSrcCfg.srcKey.Namespace, gitSrcCfg.srcKey.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sm := &SourceManager{
|
||||
srcCfg: gitSrcCfg,
|
||||
automationObjKey: originKey,
|
||||
workingDir: workDir,
|
||||
}
|
||||
return sm, nil
|
||||
}
|
||||
|
||||
// CreateWorkingDirectory creates a working directory for the SourceManager.
|
||||
func (sm SourceManager) WorkDirectory() string {
|
||||
return sm.workingDir
|
||||
}
|
||||
|
||||
// Cleanup deletes the working directory of the SourceManager.
|
||||
func (sm SourceManager) Cleanup() error {
|
||||
return os.RemoveAll(sm.workingDir)
|
||||
}
|
||||
|
||||
// SwitchBranch returns if the checkout branch and push branch are different.
|
||||
func (sm SourceManager) SwitchBranch() bool {
|
||||
return sm.srcCfg.switchBranch
|
||||
}
|
||||
|
||||
// CheckoutOption allows configuring the checkout options.
|
||||
type CheckoutOption func(*repository.CloneConfig)
|
||||
|
||||
// WithCheckoutOptionLastObserved is a CheckoutOption option to configure the
|
||||
// last observed commit.
|
||||
func WithCheckoutOptionLastObserved(commit string) CheckoutOption {
|
||||
return func(cc *repository.CloneConfig) {
|
||||
cc.LastObservedCommit = commit
|
||||
}
|
||||
}
|
||||
|
||||
// WithCheckoutOptionShallowClone is a CheckoutOption option to configure
|
||||
// shallow clone.
|
||||
func WithCheckoutOptionShallowClone() CheckoutOption {
|
||||
return func(cc *repository.CloneConfig) {
|
||||
cc.ShallowClone = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithCheckoutOptionSparseCheckoutDirectories is a CheckoutOption option to configure
|
||||
// SparseCheckoutDirectories.
|
||||
func WithCheckoutOptionSparseCheckoutDirectories(updatePath string) CheckoutOption {
|
||||
return func(cc *repository.CloneConfig) {
|
||||
cleanedPath := filepath.Clean(updatePath)
|
||||
if cleanedPath == "." {
|
||||
// Do not set SparseCheckoutDirectories if repository root is specified
|
||||
return
|
||||
}
|
||||
cc.SparseCheckoutDirectories = []string{cleanedPath}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckoutSource clones and checks out the source. If a push branch is
|
||||
// configured that doesn't match with the checkout branch, a checkout to the
|
||||
// push branch is also performed. This ensures any change and push operation
|
||||
// following the checkout happens on the push branch.
|
||||
func (sm *SourceManager) CheckoutSource(ctx context.Context, options ...CheckoutOption) (*git.Commit, error) {
|
||||
// Configuration clone options.
|
||||
cloneCfg := repository.CloneConfig{}
|
||||
if sm.srcCfg.checkoutRef != nil {
|
||||
cloneCfg.Tag = sm.srcCfg.checkoutRef.Tag
|
||||
cloneCfg.SemVer = sm.srcCfg.checkoutRef.SemVer
|
||||
cloneCfg.Commit = sm.srcCfg.checkoutRef.Commit
|
||||
cloneCfg.Branch = sm.srcCfg.checkoutRef.Branch
|
||||
}
|
||||
// Apply checkout configurations.
|
||||
for _, o := range options {
|
||||
o(&cloneCfg)
|
||||
}
|
||||
|
||||
var err error
|
||||
sm.gitClient, err = gogit.NewClient(sm.workingDir, sm.srcCfg.authOpts, sm.srcCfg.clientOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gitOpCtx, cancel := context.WithTimeout(ctx, sm.srcCfg.timeout.Duration)
|
||||
defer cancel()
|
||||
commit, err := sm.gitClient.Clone(gitOpCtx, sm.srcCfg.url, cloneCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sm.srcCfg.switchBranch {
|
||||
if err := sm.gitClient.SwitchBranch(gitOpCtx, sm.srcCfg.pushBranch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
// PushConfig configures the options used in push operation.
|
||||
type PushConfig func(*repository.PushConfig)
|
||||
|
||||
// WithPushConfigForce configures the PushConfig to use force.
|
||||
func WithPushConfigForce() PushConfig {
|
||||
return func(pc *repository.PushConfig) {
|
||||
pc.Force = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithPushConfigOptions configures the PushConfig Options that are used in
|
||||
// push.
|
||||
func WithPushConfigOptions(opts map[string]string) PushConfig {
|
||||
return func(pc *repository.PushConfig) {
|
||||
pc.Options = opts
|
||||
}
|
||||
}
|
||||
|
||||
// CommitAndPush performs a commit in the source and pushes it to the remote
|
||||
// repository.
|
||||
func (sm SourceManager) CommitAndPush(ctx context.Context, obj *imagev1.ImageUpdateAutomation, policyResult update.Result, pushOptions ...PushConfig) (*PushResult, error) {
|
||||
tracelog := log.FromContext(ctx).V(logger.TraceLevel)
|
||||
|
||||
// Make sure there were file changes that need to be committed.
|
||||
if len(policyResult.FileChanges) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Perform a Git commit.
|
||||
templateValues := &TemplateData{
|
||||
AutomationObject: sm.automationObjKey,
|
||||
Changed: policyResult,
|
||||
Values: obj.Spec.GitSpec.Commit.MessageTemplateValues,
|
||||
}
|
||||
commitMsg, err := templateMsg(obj.Spec.GitSpec.Commit.MessageTemplate, templateValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signature := git.Signature{
|
||||
Name: obj.Spec.GitSpec.Commit.Author.Name,
|
||||
Email: obj.Spec.GitSpec.Commit.Author.Email,
|
||||
When: time.Now(),
|
||||
}
|
||||
|
||||
var rev string
|
||||
var commitErr error
|
||||
rev, commitErr = sm.gitClient.Commit(
|
||||
git.Commit{
|
||||
Author: signature,
|
||||
Message: commitMsg,
|
||||
},
|
||||
repository.WithSigner(sm.srcCfg.signingEntity),
|
||||
)
|
||||
|
||||
if commitErr != nil {
|
||||
if !errors.Is(commitErr, git.ErrNoStagedFiles) {
|
||||
return nil, commitErr
|
||||
}
|
||||
log.FromContext(ctx).Info("no changes made in the source; no commit")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Push the commit to push branch.
|
||||
gitOpCtx, cancel := context.WithTimeout(ctx, sm.srcCfg.timeout.Duration)
|
||||
defer cancel()
|
||||
pushConfig := repository.PushConfig{}
|
||||
for _, po := range pushOptions {
|
||||
po(&pushConfig)
|
||||
}
|
||||
if err := sm.gitClient.Push(gitOpCtx, pushConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tracelog.Info("pushed commit to push branch", "revision", rev, "branch", sm.srcCfg.pushBranch)
|
||||
|
||||
// Push to any provided refspec.
|
||||
if obj.Spec.GitSpec.HasRefspec() {
|
||||
pushConfig.Refspecs = append(pushConfig.Refspecs, obj.Spec.GitSpec.Push.Refspec)
|
||||
if err := sm.gitClient.Push(gitOpCtx, pushConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tracelog.Info("pushed commit to refspec", "revision", rev, "refspecs", pushConfig.Refspecs)
|
||||
}
|
||||
|
||||
// Construct the result of the push operation and return.
|
||||
prOpts := []PushResultOption{WithPushResultRefspec(pushConfig.Refspecs)}
|
||||
if sm.srcCfg.switchBranch {
|
||||
prOpts = append(prOpts, WithPushResultSwitchBranch())
|
||||
}
|
||||
return NewPushResult(sm.srcCfg.pushBranch, rev, commitMsg, prOpts...)
|
||||
}
|
||||
|
||||
// templateMsg renders a msg template, returning the message or an error.
|
||||
func templateMsg(messageTemplate string, templateValues *TemplateData) (string, error) {
|
||||
if messageTemplate == "" {
|
||||
messageTemplate = defaultMessageTemplate
|
||||
}
|
||||
|
||||
// Includes only functions that are guaranteed to always evaluate to the same result for given input.
|
||||
// This removes the possibility of accidentally relying on where or when the template runs.
|
||||
// https://github.com/Masterminds/sprig/blob/3ac42c7bc5e4be6aa534e036fb19dde4a996da2e/functions.go#L70
|
||||
t, err := template.New("commit message").Funcs(sprig.HermeticTxtFuncMap()).Parse(messageTemplate)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to create commit message template from spec: %w", err)
|
||||
}
|
||||
|
||||
b := &strings.Builder{}
|
||||
if err := t.Execute(b, *templateValues); err != nil {
|
||||
if removedFieldErr := checkRemovedTemplateField(err); removedFieldErr != nil {
|
||||
return "", removedFieldErr
|
||||
}
|
||||
return "", fmt.Errorf("failed to run template from spec: %w", err)
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
// checkRemovedTemplateField checks if the template error is due to removed fields
|
||||
func checkRemovedTemplateField(err error) error {
|
||||
removedFieldChecks := []struct {
|
||||
fieldName string
|
||||
errorPattern string
|
||||
}{
|
||||
{".Updated", "can't evaluate field Updated in type source.TemplateData"},
|
||||
{".Changed.ImageResult", "can't evaluate field ImageResult in type update.Result"},
|
||||
}
|
||||
|
||||
for _, check := range removedFieldChecks {
|
||||
if strings.Contains(err.Error(), check.errorPattern) {
|
||||
return &RemovedTemplateFieldError{Field: check.fieldName}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushResultOption allows configuring the options of PushResult.
|
||||
type PushResultOption func(*PushResult)
|
||||
|
||||
// WithPushResultSwitchBranch marks the PushResult with switchBranch.
|
||||
func WithPushResultSwitchBranch() func(*PushResult) {
|
||||
return func(pr *PushResult) {
|
||||
pr.switchBranch = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithPushResultRefspec sets the refspecs in the PushResult.
|
||||
func WithPushResultRefspec(refspecs []string) func(*PushResult) {
|
||||
return func(pr *PushResult) {
|
||||
pr.refspecs = append(pr.refspecs, refspecs...)
|
||||
}
|
||||
}
|
||||
|
||||
// PushResult is the result of a push operation.
|
||||
type PushResult struct {
|
||||
commit *git.Commit
|
||||
switchBranch bool
|
||||
branch string
|
||||
refspecs []string
|
||||
creationTime *metav1.Time
|
||||
}
|
||||
|
||||
// NewPushResult returns a new PushResult.
|
||||
func NewPushResult(branch string, rev string, commitMsg string, opts ...PushResultOption) (*PushResult, error) {
|
||||
if rev == "" {
|
||||
return nil, errors.New("empty push commit revision")
|
||||
}
|
||||
|
||||
pr := &PushResult{}
|
||||
for _, o := range opts {
|
||||
o(pr)
|
||||
}
|
||||
pr.commit = &git.Commit{
|
||||
Hash: git.ExtractHashFromRevision(rev),
|
||||
Reference: plumbing.NewBranchReferenceName(branch).String(),
|
||||
Message: commitMsg,
|
||||
}
|
||||
pr.branch = branch
|
||||
pr.creationTime = &metav1.Time{Time: time.Now()}
|
||||
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// Commit returns the revision of the pushed commit.
|
||||
func (pr PushResult) Commit() *git.Commit {
|
||||
return pr.commit
|
||||
}
|
||||
|
||||
// Time returns the time at which the push was performed.
|
||||
func (pr PushResult) Time() *metav1.Time {
|
||||
return pr.creationTime
|
||||
}
|
||||
|
||||
// SwitchBranch returns if the source has different checkout and push branch.
|
||||
func (pr PushResult) SwitchBranch() bool {
|
||||
return pr.switchBranch
|
||||
}
|
||||
|
||||
// Summary returns a summary of the PushResult.
|
||||
func (pr PushResult) Summary() string {
|
||||
var summary strings.Builder
|
||||
shortCommitHash := pr.Commit().Hash.String()
|
||||
if len(shortCommitHash) > 7 {
|
||||
shortCommitHash = shortCommitHash[:7]
|
||||
}
|
||||
summary.WriteString(fmt.Sprintf("pushed commit '%s' to branch '%s'", shortCommitHash, pr.branch))
|
||||
if len(pr.refspecs) > 0 {
|
||||
summary.WriteString(fmt.Sprintf(" and refspecs '%s'", strings.Join(pr.refspecs, "', '")))
|
||||
}
|
||||
if pr.Commit().Message != "" {
|
||||
summary.WriteString(fmt.Sprintf("\n%s", pr.Commit().Message))
|
||||
}
|
||||
return summary.String()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,10 +0,0 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -1,69 +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 testutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestExpectMatchingDirectories(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
actualRoot string
|
||||
expectedRoot string
|
||||
}{
|
||||
{
|
||||
name: "same directory",
|
||||
actualRoot: "testdata/base",
|
||||
expectedRoot: "testdata/base",
|
||||
},
|
||||
{
|
||||
name: "different equivalent directories",
|
||||
actualRoot: "testdata/base",
|
||||
expectedRoot: "testdata/equiv",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
ExpectMatchingDirectories(g, tt.actualRoot, tt.expectedRoot)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffDirectories(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
// Finds files in actual a/ that weren't expected from b/.
|
||||
actualonly, _, _ := DiffDirectories("testdata/diff/a", "testdata/diff/b")
|
||||
g.Expect(actualonly).To(Equal([]string{"/only", "/onlyhere.yaml"}))
|
||||
|
||||
// Finds files in expected from a/ but not in actual b/.
|
||||
_, expectedonly, _ := DiffDirectories("testdata/diff/b", "testdata/diff/a") // NB change in order
|
||||
g.Expect(expectedonly).To(Equal([]string{"/only", "/onlyhere.yaml"}))
|
||||
|
||||
// Finds files that are different in a and b.
|
||||
_, _, diffs := DiffDirectories("testdata/diff/a", "testdata/diff/b")
|
||||
var diffpaths []string
|
||||
for _, d := range diffs {
|
||||
diffpaths = append(diffpaths, d.Path())
|
||||
}
|
||||
g.Expect(diffpaths).To(Equal([]string{"/different/content.yaml", "/dirfile"}))
|
||||
}
|
|
@ -1,481 +0,0 @@
|
|||
/*
|
||||
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 testutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/go-git/go-billy/v5/osfs"
|
||||
extgogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/cache"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
|
||||
"github.com/fluxcd/pkg/gittestserver"
|
||||
|
||||
reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
|
||||
"github.com/fluxcd/image-automation-controller/internal/constants"
|
||||
)
|
||||
|
||||
const (
|
||||
signingSecretKey = "git.asc"
|
||||
signingPassphraseKey = "passphrase"
|
||||
)
|
||||
|
||||
func CheckoutBranch(g *WithT, repo *extgogit.Repository, branch string) {
|
||||
g.THelper()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func ReplaceMarker(path string, policyKey types.NamespacedName) error {
|
||||
return ReplaceMarkerWithMarker(path, policyKey, "SETTER_SITE")
|
||||
}
|
||||
|
||||
func ReplaceMarkerWithMarker(path string, policyKey types.NamespacedName, marker string) error {
|
||||
filebytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newfilebytes := bytes.ReplaceAll(filebytes, []byte(marker), []byte(setterRef(policyKey)))
|
||||
if err = os.WriteFile(path, newfilebytes, os.FileMode(0666)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setterRef(name types.NamespacedName) string {
|
||||
return fmt.Sprintf(`{"%s": "%s:%s"}`, constants.SetterShortHand, name.Namespace, name.Name)
|
||||
}
|
||||
|
||||
func CommitInRepo(ctx context.Context, g *WithT, repoURL, branch, remote, msg string, changeFiles func(path string)) plumbing.Hash {
|
||||
g.THelper()
|
||||
|
||||
repo, cloneDir, err := Clone(ctx, repoURL, branch, remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer func() { os.RemoveAll(cloneDir) }()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
changeFiles(wt.Filesystem.Root())
|
||||
|
||||
id := CommitWorkDir(g, repo, branch, msg)
|
||||
|
||||
origin, err := repo.Remote(remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(origin.Push(&extgogit.PushOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branch))},
|
||||
})).To(Succeed())
|
||||
return id
|
||||
}
|
||||
|
||||
func WaitForNewHead(g *WithT, repo *extgogit.Repository, branch, remote, preChangeHash string) {
|
||||
g.THelper()
|
||||
|
||||
var commitToResetTo *object.Commit
|
||||
|
||||
origin, err := repo.Remote(remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Now try to fetch new commits from that remote branch
|
||||
g.Eventually(func() bool {
|
||||
err := origin.Fetch(&extgogit.FetchOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branch))},
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
remoteHeadRef, err := repo.Head()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
remoteHeadHash := remoteHeadRef.Hash()
|
||||
|
||||
if preChangeHash != remoteHeadHash.String() {
|
||||
commitToResetTo, _ = repo.CommitObject(remoteHeadHash)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, 10*time.Second, time.Second).Should(BeTrue())
|
||||
|
||||
if commitToResetTo != nil {
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// New commits in the remote branch -- reset the working tree head
|
||||
// to that. Note this does not create a local branch tracking the
|
||||
// remote, so it is a detached head.
|
||||
g.Expect(wt.Reset(&extgogit.ResetOptions{
|
||||
Commit: commitToResetTo.Hash,
|
||||
Mode: extgogit.HardReset,
|
||||
})).To(Succeed())
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise a git server with a repo including the files in dir.
|
||||
func InitGitRepo(g *WithT, gitServer *gittestserver.GitServer, fixture, branch, repoPath string) *extgogit.Repository {
|
||||
g.THelper()
|
||||
|
||||
workDir, err := securejoin.SecureJoin(gitServer.Root(), repoPath)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
repo := InitGitRepoPlain(g, fixture, workDir)
|
||||
|
||||
headRef, err := repo.Head()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
ref := plumbing.NewHashReference(
|
||||
plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branch)),
|
||||
headRef.Hash())
|
||||
|
||||
g.Expect(repo.Storer.SetReference(ref)).ToNot(HaveOccurred())
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func InitGitRepoPlain(g *WithT, fixture, repoPath string) *extgogit.Repository {
|
||||
g.THelper()
|
||||
|
||||
wt := osfs.New(repoPath)
|
||||
dot := osfs.New(filepath.Join(repoPath, extgogit.GitDirName))
|
||||
storer := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
|
||||
|
||||
repo, err := extgogit.Init(storer, wt)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(copyDir(fixture, repoPath)).ToNot(HaveOccurred())
|
||||
|
||||
_ = CommitWorkDir(g, repo, "main", "Initial commit")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func HeadFromBranch(repo *extgogit.Repository, branchName string) (*object.Commit, error) {
|
||||
ref, err := repo.Storer.Reference(plumbing.ReferenceName("refs/heads/" + branchName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo.CommitObject(ref.Hash())
|
||||
}
|
||||
|
||||
func CommitWorkDir(g *WithT, repo *extgogit.Repository, branchName, message string) plumbing.Hash {
|
||||
g.THelper()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Checkout to an existing branch. If this is the first commit,
|
||||
// this is a no-op.
|
||||
_ = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.ReferenceName("refs/heads/" + branchName),
|
||||
})
|
||||
|
||||
status, err := wt.Status()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
for file := range status {
|
||||
wt.Add(file)
|
||||
}
|
||||
|
||||
sig := mockSignature(time.Now())
|
||||
c, err := wt.Commit(message, &extgogit.CommitOptions{
|
||||
All: true,
|
||||
Author: sig,
|
||||
Committer: sig,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
_, err = repo.Branch(branchName)
|
||||
if err == extgogit.ErrBranchNotFound {
|
||||
ref := plumbing.NewHashReference(
|
||||
plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branchName)), c)
|
||||
err = repo.Storer.SetReference(ref)
|
||||
}
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Now the target branch exists, we can checkout to it.
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.ReferenceName("refs/heads/" + branchName),
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func TagCommit(g *WithT, repo *extgogit.Repository, commit plumbing.Hash, annotated bool, tag string, time time.Time) (*plumbing.Reference, error) {
|
||||
g.THelper()
|
||||
|
||||
var opts *extgogit.CreateTagOptions
|
||||
if annotated {
|
||||
opts = &extgogit.CreateTagOptions{
|
||||
Tagger: mockSignature(time),
|
||||
Message: "Annotated tag for: " + tag,
|
||||
}
|
||||
}
|
||||
return repo.CreateTag(tag, commit, opts)
|
||||
}
|
||||
|
||||
func copyDir(src string, dest string) error {
|
||||
file, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !file.IsDir() {
|
||||
return fmt.Errorf("source %q must be a directory", file.Name())
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(dest, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
srcFile := filepath.Join(src, f.Name())
|
||||
destFile := filepath.Join(dest, f.Name())
|
||||
|
||||
if f.IsDir() {
|
||||
if err = copyDir(srcFile, destFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.IsDir() {
|
||||
// ignore symlinks
|
||||
if f.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.WriteFile(destFile, content, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func BranchRefName(branch string) string {
|
||||
return fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)
|
||||
}
|
||||
|
||||
func mockSignature(time time.Time) *object.Signature {
|
||||
return &object.Signature{
|
||||
Name: "Jane Doe",
|
||||
Email: "author@example.com",
|
||||
When: time,
|
||||
}
|
||||
}
|
||||
|
||||
func Clone(ctx context.Context, repoURL, branchName, remote string) (*extgogit.Repository, string, error) {
|
||||
dir, err := os.MkdirTemp("", "iac-clone-*")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
opts := &extgogit.CloneOptions{
|
||||
URL: repoURL,
|
||||
RemoteName: remote,
|
||||
ReferenceName: plumbing.NewBranchReferenceName(branchName),
|
||||
}
|
||||
|
||||
wt := osfs.New(dir, osfs.WithBoundOS())
|
||||
dot := osfs.New(filepath.Join(dir, extgogit.GitDirName), osfs.WithBoundOS())
|
||||
storer := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
|
||||
|
||||
repo, err := extgogit.Clone(storer, wt, opts)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
w, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
err = w.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branchName),
|
||||
Create: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return repo, dir, nil
|
||||
}
|
||||
|
||||
func CommitIdFromBranch(repo *extgogit.Repository, branchName string) string {
|
||||
commitId := ""
|
||||
head, err := HeadFromBranch(repo, branchName)
|
||||
|
||||
if err == nil {
|
||||
commitId = head.Hash.String()
|
||||
}
|
||||
return commitId
|
||||
}
|
||||
|
||||
func GetRemoteHead(repo *extgogit.Repository, branchName, remote string) (plumbing.Hash, error) {
|
||||
rmt, err := repo.Remote(remote)
|
||||
if err != nil {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
err = rmt.Fetch(&extgogit.FetchOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branchName))},
|
||||
})
|
||||
if err != nil && !errors.Is(err, extgogit.NoErrAlreadyUpToDate) {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
remoteHeadRef, err := HeadFromBranch(repo, branchName)
|
||||
if err != nil {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
return remoteHeadRef.Hash, nil
|
||||
}
|
||||
|
||||
// SetUpGitTestServer creates and returns a git test server. The caller must
|
||||
// ensure it's stopped and cleaned up.
|
||||
func SetUpGitTestServer(g *WithT) *gittestserver.GitServer {
|
||||
g.THelper()
|
||||
|
||||
gitServer, err := gittestserver.NewTempGitServer()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
username := rand.String(5)
|
||||
password := rand.String(5)
|
||||
|
||||
gitServer.Auth(username, password)
|
||||
gitServer.AutoCreate()
|
||||
g.Expect(gitServer.StartHTTP()).ToNot(HaveOccurred())
|
||||
gitServer.KeyDir(filepath.Join(gitServer.Root(), "keys"))
|
||||
g.Expect(gitServer.ListenSSH()).ToNot(HaveOccurred())
|
||||
return gitServer
|
||||
}
|
||||
|
||||
func GetSigningKeyPairSecret(g *WithT, name, namespace string) (*corev1.Secret, *openpgp.Entity) {
|
||||
g.THelper()
|
||||
|
||||
passphrase := "abcde12345"
|
||||
pgpEntity, key := GetSigningKeyPair(g, passphrase)
|
||||
|
||||
// Create the secret containing signing key.
|
||||
sec := &corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
signingSecretKey: key,
|
||||
signingPassphraseKey: []byte(passphrase),
|
||||
},
|
||||
}
|
||||
sec.Name = name
|
||||
sec.Namespace = namespace
|
||||
return sec, pgpEntity
|
||||
}
|
||||
|
||||
func GetSigningKeyPair(g *WithT, passphrase string) (*openpgp.Entity, []byte) {
|
||||
g.THelper()
|
||||
|
||||
pgpEntity, err := openpgp.NewEntity("", "", "", nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Configure OpenPGP armor encoder.
|
||||
b := bytes.NewBuffer(nil)
|
||||
w, err := armor.Encode(b, openpgp.PrivateKeyType, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
// Serialize private key.
|
||||
g.Expect(pgpEntity.SerializePrivate(w, nil)).To(Succeed())
|
||||
g.Expect(w.Close()).To(Succeed())
|
||||
|
||||
if passphrase != "" {
|
||||
g.Expect(pgpEntity.PrivateKey.Encrypt([]byte(passphrase))).To(Succeed())
|
||||
}
|
||||
|
||||
return pgpEntity, b.Bytes()
|
||||
}
|
||||
|
||||
func ImageToRef(image string) *reflectorv1.ImageRef {
|
||||
var digest string
|
||||
|
||||
if idx := strings.LastIndex(image, "@"); idx != -1 {
|
||||
image, digest = image[:idx], image[idx+1:]
|
||||
}
|
||||
|
||||
var tag string
|
||||
|
||||
if idx := strings.LastIndex(image, ":"); idx != -1 {
|
||||
image, tag = image[:idx], image[idx+1:]
|
||||
}
|
||||
|
||||
return &reflectorv1.ImageRef{
|
||||
Name: image,
|
||||
Tag: tag,
|
||||
Digest: digest,
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
Copyright 2025 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 update
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
. "github.com/onsi/gomega"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
||||
)
|
||||
|
||||
func TestScreeningLocalReader(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
r := ScreeningLocalReader{
|
||||
Path: "testdata/setters/original",
|
||||
Token: "$imagepolicy",
|
||||
Trace: logr.Discard(),
|
||||
}
|
||||
nodes, err := r.Read()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
// the test fixture has four files that contain the marker:
|
||||
// - marked.yaml
|
||||
// - otherns.yaml
|
||||
// - kustomization.yml
|
||||
// - Kustomization
|
||||
g.Expect(len(nodes)).To(Equal(4))
|
||||
filesSeen := map[string]struct{}{}
|
||||
for i := range nodes {
|
||||
path, _, err := kioutil.GetFileAnnotations(nodes[i])
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
filesSeen[path] = struct{}{}
|
||||
}
|
||||
g.Expect(filesSeen).To(Equal(map[string]struct{}{
|
||||
"marked.yaml": {},
|
||||
"otherns.yaml": {},
|
||||
"kustomization.yml": {},
|
||||
"Kustomization": {},
|
||||
}))
|
||||
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
/*
|
||||
Copyright 2025 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 update
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestSetAllCallbackAccept(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
object *yaml.RNode
|
||||
settersSchema *spec.Schema
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "Accept - Scalar Node",
|
||||
object: yaml.NewRNode(&yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: "test",
|
||||
}),
|
||||
settersSchema: &spec.Schema{},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Accept - Scalar Node - Error",
|
||||
object: yaml.NewRNode(&yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: "test",
|
||||
}),
|
||||
settersSchema: nil,
|
||||
expectedError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
callbackInstance := SetAllCallback{
|
||||
SettersSchema: test.settersSchema,
|
||||
Trace: logr.Discard(),
|
||||
}
|
||||
|
||||
err := accept(&callbackInstance, test.object, "", test.settersSchema)
|
||||
g := NewWithT(t)
|
||||
if test.expectedError {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
} else {
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExtFromSchema(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
schema *spec.Schema
|
||||
expectedExtension *extension
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "Extension Present",
|
||||
schema: &spec.Schema{
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: map[string]interface{}{
|
||||
K8sCliExtensionKey: &extension{
|
||||
Setter: &setter{
|
||||
Name: "testSetter",
|
||||
Value: "testValue",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedExtension: &extension{
|
||||
Setter: &setter{
|
||||
Name: "testSetter",
|
||||
Value: "testValue",
|
||||
},
|
||||
},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Extension Not Present",
|
||||
schema: &spec.Schema{},
|
||||
expectedError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
ext, err := getExtFromSchema(test.schema)
|
||||
|
||||
if test.expectedError {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
} else {
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(ext).To(Equal(test.expectedExtension))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
Copyright 2025 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 update
|
||||
|
||||
import (
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// ImageRef represents the image reference used to replace a field
|
||||
// value in an update.
|
||||
type ImageRef interface {
|
||||
// String returns a string representation of the image ref as it
|
||||
// is used in the update; e.g., "helloworld:v1.0.1"
|
||||
String() string
|
||||
// Identifier returns the tag or digest; e.g., "v1.0.1"
|
||||
Identifier() string
|
||||
// Repository returns the repository component of the ImageRef,
|
||||
// with an implied defaults, e.g., "library/helloworld"
|
||||
Repository() string
|
||||
// Registry returns the registry component of the ImageRef, e.g.,
|
||||
// "index.docker.io"
|
||||
Registry() string
|
||||
// Name gives the fully-qualified reference name, e.g.,
|
||||
// "index.docker.io/library/helloworld:v1.0.1"
|
||||
Name() string
|
||||
// Policy gives the namespaced name of the image policy that led
|
||||
// to the update.
|
||||
Policy() types.NamespacedName
|
||||
}
|
||||
|
||||
type imageRef struct {
|
||||
name.Reference
|
||||
policy types.NamespacedName
|
||||
}
|
||||
|
||||
// Policy gives the namespaced name of the policy that led to the
|
||||
// update.
|
||||
func (i imageRef) Policy() types.NamespacedName {
|
||||
return i.policy
|
||||
}
|
||||
|
||||
// Repository gives the repository component of the image ref.
|
||||
func (i imageRef) Repository() string {
|
||||
return i.Context().RepositoryStr()
|
||||
}
|
||||
|
||||
// Registry gives the registry component of the image ref.
|
||||
func (i imageRef) Registry() string {
|
||||
return i.Context().Registry.String()
|
||||
}
|
||||
|
||||
// ObjectIdentifier holds the identifying data for a particular
|
||||
// object. This won't always have a name (e.g., a kustomization.yaml).
|
||||
type ObjectIdentifier struct {
|
||||
yaml.ResourceIdentifier
|
||||
}
|
||||
|
||||
// Result contains the file changes made during the update. It contains
|
||||
// details about the exact changes made to the files and the objects in them.
|
||||
// It has a nested structure file->objects->changes.
|
||||
type Result struct {
|
||||
FileChanges map[string]ObjectChanges
|
||||
}
|
||||
|
||||
// ObjectChanges contains all the changes made to objects.
|
||||
type ObjectChanges map[ObjectIdentifier][]Change
|
||||
|
||||
// Change contains the setter that resulted in a Change, the old and the new
|
||||
// value after the Change.
|
||||
type Change struct {
|
||||
OldValue string
|
||||
NewValue string
|
||||
Setter string
|
||||
}
|
||||
|
||||
// AddChange adds changes to Result for a given file, object and changes
|
||||
// associated with it.
|
||||
func (r *Result) AddChange(file string, objectID ObjectIdentifier, changes ...Change) {
|
||||
if r.FileChanges == nil {
|
||||
r.FileChanges = map[string]ObjectChanges{}
|
||||
}
|
||||
// Create an entry for the file if not present.
|
||||
_, ok := r.FileChanges[file]
|
||||
if !ok {
|
||||
r.FileChanges[file] = ObjectChanges{}
|
||||
}
|
||||
// Append to the changes for the object.
|
||||
r.FileChanges[file][objectID] = append(r.FileChanges[file][objectID], changes...)
|
||||
}
|
||||
|
||||
// Changes returns all the changes that were made in at least one update.
|
||||
func (r Result) Changes() []Change {
|
||||
seen := make(map[Change]struct{})
|
||||
var result []Change
|
||||
for _, objChanges := range r.FileChanges {
|
||||
for _, changes := range objChanges {
|
||||
for _, change := range changes {
|
||||
if _, ok := seen[change]; !ok {
|
||||
seen[change] = struct{}{}
|
||||
result = append(result, change)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Objects returns ObjectChanges, regardless of which file they appear in.
|
||||
func (r Result) Objects() ObjectChanges {
|
||||
result := make(ObjectChanges)
|
||||
for _, objChanges := range r.FileChanges {
|
||||
for obj, change := range objChanges {
|
||||
result[obj] = change
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
/*
|
||||
Copyright 2025 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 update
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// mustRef creates an imageRef for use in tests. It panics if the ref
|
||||
// given is invalid.
|
||||
func mustRef(ref string) imageRef {
|
||||
r, err := name.ParseReference(ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return imageRef{r, types.NamespacedName{}}
|
||||
}
|
||||
|
||||
func TestMustRef(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
t.Run("gives each component of an image ref", func(t *testing.T) {
|
||||
ref := mustRef("helloworld:v1.0.1")
|
||||
g.Expect(ref.String()).To(Equal("helloworld:v1.0.1"))
|
||||
g.Expect(ref.Identifier()).To(Equal("v1.0.1"))
|
||||
g.Expect(ref.Repository()).To(Equal("library/helloworld"))
|
||||
g.Expect(ref.Registry()).To(Equal("index.docker.io"))
|
||||
g.Expect(ref.Name()).To(Equal("index.docker.io/library/helloworld:v1.0.1"))
|
||||
})
|
||||
|
||||
t.Run("deals with hostnames and digests", func(t *testing.T) {
|
||||
image := "localhost:5000/org/helloworld@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be"
|
||||
ref := mustRef(image)
|
||||
g.Expect(ref.String()).To(Equal(image))
|
||||
g.Expect(ref.Identifier()).To(Equal("sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be"))
|
||||
g.Expect(ref.Repository()).To(Equal("org/helloworld"))
|
||||
g.Expect(ref.Registry()).To(Equal("localhost:5000"))
|
||||
g.Expect(ref.Name()).To(Equal(image))
|
||||
})
|
||||
}
|
||||
|
||||
func TestResult(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
var result Result
|
||||
objectNames := []ObjectIdentifier{
|
||||
{yaml.ResourceIdentifier{
|
||||
NameMeta: yaml.NameMeta{Namespace: "ns", Name: "foo"},
|
||||
}},
|
||||
{yaml.ResourceIdentifier{
|
||||
NameMeta: yaml.NameMeta{Namespace: "ns", Name: "bar"},
|
||||
}},
|
||||
}
|
||||
|
||||
result.AddChange("foo.yaml", objectNames[0], Change{
|
||||
OldValue: "aaa",
|
||||
NewValue: "bbb",
|
||||
Setter: "foo-ns:policy:name",
|
||||
})
|
||||
result.AddChange("bar.yaml", objectNames[1], Change{
|
||||
OldValue: "cccc:v1.0",
|
||||
NewValue: "cccc:v1.2",
|
||||
Setter: "foo-ns:policy",
|
||||
})
|
||||
|
||||
result = Result{
|
||||
FileChanges: map[string]ObjectChanges{
|
||||
"foo.yaml": {
|
||||
objectNames[0]: []Change{
|
||||
{
|
||||
OldValue: "aaa",
|
||||
NewValue: "bbb",
|
||||
Setter: "foo-ns:policy:name",
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar.yaml": {
|
||||
objectNames[1]: []Change{
|
||||
{
|
||||
OldValue: "cccc:v1.0",
|
||||
NewValue: "cccc:v1.2",
|
||||
Setter: "foo-ns:policy",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g.Expect(result.Changes()).To(ContainElements([]Change{
|
||||
{
|
||||
OldValue: "aaa",
|
||||
NewValue: "bbb",
|
||||
Setter: "foo-ns:policy:name",
|
||||
},
|
||||
{
|
||||
OldValue: "cccc:v1.0",
|
||||
NewValue: "cccc:v1.2",
|
||||
Setter: "foo-ns:policy",
|
||||
},
|
||||
}))
|
||||
g.Expect(result.Objects()).To(Equal(ObjectChanges{
|
||||
objectNames[0]: []Change{
|
||||
{
|
||||
OldValue: "aaa",
|
||||
NewValue: "bbb",
|
||||
Setter: "foo-ns:policy:name",
|
||||
},
|
||||
},
|
||||
objectNames[1]: []Change{
|
||||
{
|
||||
OldValue: "cccc:v1.0",
|
||||
NewValue: "cccc:v1.2",
|
||||
Setter: "foo-ns:policy",
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
# This is not intended to be a working kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- unimportant.yaml
|
||||
images:
|
||||
- name: container
|
||||
newName: index.repo.fake/updated # {"$imagepolicy": "automation-ns:policy:name"}
|
||||
newTag: v1.0.1 # {"$imagepolicy": "automation-ns:policy:tag"}
|
||||
newDigest: sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be # {"$imagepolicy": "automation-ns:policy-with-digest:digest"}
|
||||
# Prove fix for https://github.com/fluxcd/flux2/issues/3284
|
||||
patches:
|
||||
- patch: |
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/volumeMounts
|
||||
value:
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/configuration/appConfigMaps/WF
|
||||
name: wf-config
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/configuration/appConfigMaps/xxx_config
|
||||
name: xxx-config
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/log
|
||||
name: wildfly-standalone-log
|
||||
target:
|
||||
group: apps
|
||||
version: v1
|
||||
kind: Deployment
|
||||
name: sxxxxdadminservice
|
||||
image: image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be # {"$imagepolicy": "automation-ns:policy-with-digest"}
|
|
@ -1,16 +0,0 @@
|
|||
apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: bar
|
||||
spec:
|
||||
schedule: "*/1 * * * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: c
|
||||
image: index.repo.fake/updated:v1.0.1 # {"$imagepolicy": "automation-ns:policy"}
|
||||
- name: d
|
||||
image: image:v1.0.0 # {"$imagepolicy": "automation-ns:unchanged"}
|
|
@ -1,9 +0,0 @@
|
|||
# This is not intended to be a working kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- irrelevant.yaml
|
||||
images:
|
||||
- name: container
|
||||
newName: replaced # {"$imagepolicy": "automation-ns:policy:name"}
|
||||
newTag: v1 # {"$imagepolicy": "automation-ns:policy:tag"}
|
|
@ -1,28 +0,0 @@
|
|||
# This is not intended to be a working kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- unimportant.yaml
|
||||
images:
|
||||
- name: container
|
||||
newName: replaced # {"$imagepolicy": "automation-ns:policy:name"}
|
||||
newTag: v1 # {"$imagepolicy": "automation-ns:policy:tag"}
|
||||
newDigest: sha256:1234567890abcdef # {"$imagepolicy": "automation-ns:policy-with-digest:digest"}
|
||||
# Prove fix for https://github.com/fluxcd/flux2/issues/3284
|
||||
patches:
|
||||
- patch: |
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/volumeMounts
|
||||
value:
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/configuration/appConfigMaps/WF
|
||||
name: wf-config
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/configuration/appConfigMaps/xxx_config
|
||||
name: xxx-config
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/log
|
||||
name: wildfly-standalone-log
|
||||
target:
|
||||
group: apps
|
||||
version: v1
|
||||
kind: Deployment
|
||||
name: sxxxxdadminservice
|
||||
image: image # {"$imagepolicy": "automation-ns:policy-with-digest"}
|
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
Copyright 2025 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 update
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
|
||||
reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
|
||||
"github.com/fluxcd/image-automation-controller/internal/testutil"
|
||||
)
|
||||
|
||||
func TestUpdateWithSetters(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
policies := []reflectorv1.ImagePolicy{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "automation-ns",
|
||||
Name: "policy",
|
||||
},
|
||||
Status: reflectorv1.ImagePolicyStatus{
|
||||
LatestRef: testutil.ImageToRef("index.repo.fake/updated:v1.0.1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "automation-ns",
|
||||
Name: "unchanged",
|
||||
},
|
||||
Status: reflectorv1.ImagePolicyStatus{
|
||||
LatestRef: testutil.ImageToRef("image:v1.0.0"),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "automation-ns",
|
||||
Name: "policy-with-digest",
|
||||
},
|
||||
Status: reflectorv1.ImagePolicyStatus{
|
||||
LatestRef: testutil.ImageToRef("image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Test Result.
|
||||
tmp := t.TempDir()
|
||||
result, err := UpdateWithSetters(logr.Discard(), "testdata/setters/original", tmp, policies)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
testutil.ExpectMatchingDirectories(g, tmp, "testdata/setters/expected")
|
||||
|
||||
kustomizeResourceID := ObjectIdentifier{yaml.ResourceIdentifier{
|
||||
TypeMeta: yaml.TypeMeta{
|
||||
APIVersion: "kustomize.config.k8s.io/v1beta1",
|
||||
Kind: "Kustomization",
|
||||
},
|
||||
}}
|
||||
markedResourceID := ObjectIdentifier{yaml.ResourceIdentifier{
|
||||
TypeMeta: yaml.TypeMeta{
|
||||
APIVersion: "batch/v1beta1",
|
||||
Kind: "CronJob",
|
||||
},
|
||||
NameMeta: yaml.NameMeta{
|
||||
Namespace: "bar",
|
||||
Name: "foo",
|
||||
},
|
||||
}}
|
||||
|
||||
expectedResult := Result{
|
||||
FileChanges: map[string]ObjectChanges{
|
||||
"kustomization.yml": {
|
||||
kustomizeResourceID: []Change{
|
||||
{
|
||||
OldValue: "replaced",
|
||||
NewValue: "index.repo.fake/updated",
|
||||
Setter: "automation-ns:policy:name",
|
||||
},
|
||||
{
|
||||
OldValue: "v1",
|
||||
NewValue: "v1.0.1",
|
||||
Setter: "automation-ns:policy:tag",
|
||||
},
|
||||
{
|
||||
OldValue: "sha256:1234567890abcdef",
|
||||
NewValue: "sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be",
|
||||
Setter: "automation-ns:policy-with-digest:digest",
|
||||
},
|
||||
{
|
||||
OldValue: "image",
|
||||
NewValue: "image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be",
|
||||
Setter: "automation-ns:policy-with-digest",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Kustomization": {
|
||||
kustomizeResourceID: []Change{
|
||||
{
|
||||
OldValue: "replaced",
|
||||
NewValue: "index.repo.fake/updated",
|
||||
Setter: "automation-ns:policy:name",
|
||||
},
|
||||
{
|
||||
OldValue: "v1",
|
||||
NewValue: "v1.0.1",
|
||||
Setter: "automation-ns:policy:tag",
|
||||
},
|
||||
},
|
||||
},
|
||||
"marked.yaml": {
|
||||
markedResourceID: []Change{
|
||||
{
|
||||
OldValue: "image:v1.0.0",
|
||||
NewValue: "index.repo.fake/updated:v1.0.1",
|
||||
Setter: "automation-ns:policy",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g.Expect(result).To(Equal(expectedResult))
|
||||
}
|
191
main.go
191
main.go
|
@ -21,46 +21,29 @@ import (
|
|||
"os"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
"k8s.io/utils/pointer"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
ctrlcfg "sigs.k8s.io/controller-runtime/pkg/config"
|
||||
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
|
||||
reflectorv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
"github.com/fluxcd/pkg/auth"
|
||||
cache "github.com/fluxcd/pkg/cache"
|
||||
"github.com/fluxcd/pkg/runtime/acl"
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||
"github.com/fluxcd/pkg/runtime/client"
|
||||
helper "github.com/fluxcd/pkg/runtime/controller"
|
||||
"github.com/fluxcd/pkg/runtime/events"
|
||||
feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||
"github.com/fluxcd/pkg/runtime/leaderelection"
|
||||
"github.com/fluxcd/pkg/runtime/logger"
|
||||
"github.com/fluxcd/pkg/runtime/metrics"
|
||||
"github.com/fluxcd/pkg/runtime/pprof"
|
||||
"github.com/fluxcd/pkg/runtime/probes"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
"github.com/fluxcd/pkg/git"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/features"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
// +kubebuilder:scaffold:imports
|
||||
"github.com/fluxcd/image-automation-controller/internal/controller"
|
||||
"github.com/fluxcd/image-automation-controller/controllers"
|
||||
)
|
||||
|
||||
const (
|
||||
controllerName = "image-automation-controller"
|
||||
)
|
||||
const controllerName = "image-automation-controller"
|
||||
|
||||
var (
|
||||
scheme = runtime.NewScheme()
|
||||
|
@ -69,184 +52,86 @@ var (
|
|||
|
||||
func init() {
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
utilruntime.Must(reflectorv1.AddToScheme(scheme))
|
||||
utilruntime.Must(imagev1_reflect.AddToScheme(scheme))
|
||||
utilruntime.Must(sourcev1.AddToScheme(scheme))
|
||||
utilruntime.Must(imagev1.AddToScheme(scheme))
|
||||
// +kubebuilder:scaffold:scheme
|
||||
}
|
||||
|
||||
func main() {
|
||||
const (
|
||||
tokenCacheDefaultMaxSize = 100
|
||||
)
|
||||
|
||||
var (
|
||||
metricsAddr string
|
||||
eventsAddr string
|
||||
healthAddr string
|
||||
clientOptions client.Options
|
||||
aclOptions acl.Options
|
||||
logOptions logger.Options
|
||||
leaderElectionOptions leaderelection.Options
|
||||
rateLimiterOptions helper.RateLimiterOptions
|
||||
featureGates feathelper.FeatureGates
|
||||
watchOptions helper.WatchOptions
|
||||
watchAllNamespaces bool
|
||||
concurrent int
|
||||
tokenCacheOptions cache.TokenFlags
|
||||
defaultServiceAccount string
|
||||
)
|
||||
|
||||
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
flag.StringVar(&eventsAddr, "events-addr", "", "The address of the events receiver.")
|
||||
flag.StringVar(&healthAddr, "health-addr", ":9440", "The address the health endpoint binds to.")
|
||||
flag.BoolVar(&watchAllNamespaces, "watch-all-namespaces", true,
|
||||
"Watch for custom resources in all namespaces, if set to false it will only watch the runtime namespace.")
|
||||
flag.IntVar(&concurrent, "concurrent", 4, "The number of concurrent resource reconciles.")
|
||||
flag.StringVar(&defaultServiceAccount, auth.ControllerFlagDefaultServiceAccount,
|
||||
"", "Default service account to use for workload identity when not specified in resources.")
|
||||
flag.StringSliceVar(&git.KexAlgos, "ssh-kex-algos", []string{},
|
||||
"The list of key exchange algorithms to use for ssh connections, arranged from most preferred to the least.")
|
||||
flag.StringSliceVar(&git.HostKeyAlgos, "ssh-hostkey-algos", []string{},
|
||||
"The list of hostkey algorithms to use for ssh connections, arranged from most preferred to the least.")
|
||||
|
||||
clientOptions.BindFlags(flag.CommandLine)
|
||||
logOptions.BindFlags(flag.CommandLine)
|
||||
leaderElectionOptions.BindFlags(flag.CommandLine)
|
||||
aclOptions.BindFlags(flag.CommandLine)
|
||||
rateLimiterOptions.BindFlags(flag.CommandLine)
|
||||
featureGates.BindFlags(flag.CommandLine)
|
||||
watchOptions.BindFlags(flag.CommandLine)
|
||||
tokenCacheOptions.BindFlags(flag.CommandLine, tokenCacheDefaultMaxSize)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
logger.SetLogger(logger.NewLogger(logOptions))
|
||||
log := logger.NewLogger(logOptions)
|
||||
ctrl.SetLogger(log)
|
||||
|
||||
err := featureGates.WithLogger(setupLog).
|
||||
SupportedFeatures(features.FeatureGates())
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to load feature gates")
|
||||
os.Exit(1)
|
||||
var eventRecorder *events.Recorder
|
||||
if eventsAddr != "" {
|
||||
if er, err := events.NewRecorder(eventsAddr, controllerName); err != nil {
|
||||
setupLog.Error(err, "unable to create event recorder")
|
||||
os.Exit(1)
|
||||
} else {
|
||||
eventRecorder = er
|
||||
}
|
||||
}
|
||||
|
||||
switch enabled, err := features.Enabled(auth.FeatureGateObjectLevelWorkloadIdentity); {
|
||||
case err != nil:
|
||||
setupLog.Error(err, "unable to check feature gate "+auth.FeatureGateObjectLevelWorkloadIdentity)
|
||||
os.Exit(1)
|
||||
case enabled:
|
||||
auth.EnableObjectLevelWorkloadIdentity()
|
||||
}
|
||||
|
||||
if defaultServiceAccount != "" {
|
||||
auth.SetDefaultServiceAccount(defaultServiceAccount)
|
||||
}
|
||||
|
||||
if auth.InconsistentObjectLevelConfiguration() {
|
||||
setupLog.Error(auth.ErrInconsistentObjectLevelConfiguration, "invalid configuration")
|
||||
os.Exit(1)
|
||||
}
|
||||
metricsRecorder := metrics.NewRecorder()
|
||||
ctrlmetrics.Registry.MustRegister(metricsRecorder.Collectors()...)
|
||||
|
||||
watchNamespace := ""
|
||||
if !watchOptions.AllNamespaces {
|
||||
if !watchAllNamespaces {
|
||||
watchNamespace = os.Getenv("RUNTIME_NAMESPACE")
|
||||
}
|
||||
|
||||
var disableCacheFor []ctrlclient.Object
|
||||
shouldCache, err := features.Enabled(features.CacheSecretsAndConfigMaps)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to check feature gate "+features.CacheSecretsAndConfigMaps)
|
||||
os.Exit(1)
|
||||
}
|
||||
if !shouldCache {
|
||||
disableCacheFor = append(disableCacheFor, &corev1.Secret{}, &corev1.ConfigMap{})
|
||||
}
|
||||
|
||||
restConfig := client.GetConfigOrDie(clientOptions)
|
||||
|
||||
watchSelector, err := helper.GetWatchSelector(watchOptions)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to configure watch label selector for manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
leaderElectionID := fmt.Sprintf("%s-leader-election", controllerName)
|
||||
if watchOptions.LabelSelector != "" {
|
||||
leaderElectionID = leaderelection.GenerateID(leaderElectionID, watchOptions.LabelSelector)
|
||||
}
|
||||
|
||||
mgrConfig := ctrl.Options{
|
||||
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
HealthProbeBindAddress: healthAddr,
|
||||
Port: 9443,
|
||||
LeaderElection: leaderElectionOptions.Enable,
|
||||
LeaderElectionReleaseOnCancel: leaderElectionOptions.ReleaseOnCancel,
|
||||
LeaseDuration: &leaderElectionOptions.LeaseDuration,
|
||||
RenewDeadline: &leaderElectionOptions.RenewDeadline,
|
||||
RetryPeriod: &leaderElectionOptions.RetryPeriod,
|
||||
LeaderElectionID: leaderElectionID,
|
||||
Client: ctrlclient.Options{
|
||||
Cache: &ctrlclient.CacheOptions{
|
||||
DisableFor: disableCacheFor,
|
||||
},
|
||||
},
|
||||
Cache: ctrlcache.Options{
|
||||
ByObject: map[ctrlclient.Object]ctrlcache.ByObject{
|
||||
&imagev1.ImageUpdateAutomation{}: {Label: watchSelector},
|
||||
},
|
||||
},
|
||||
Metrics: metricsserver.Options{
|
||||
BindAddress: metricsAddr,
|
||||
ExtraHandlers: pprof.GetHandlers(),
|
||||
},
|
||||
Controller: ctrlcfg.Controller{
|
||||
RecoverPanic: pointer.Bool(true),
|
||||
MaxConcurrentReconciles: concurrent,
|
||||
},
|
||||
}
|
||||
|
||||
if watchNamespace != "" {
|
||||
mgrConfig.Cache.DefaultNamespaces = map[string]ctrlcache.Config{
|
||||
watchNamespace: ctrlcache.Config{},
|
||||
}
|
||||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(restConfig, mgrConfig)
|
||||
LeaderElectionID: fmt.Sprintf("%s-leader-election", controllerName),
|
||||
Namespace: watchNamespace,
|
||||
})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
probes.SetupChecks(mgr, setupLog)
|
||||
pprof.SetupHandlers(mgr, setupLog)
|
||||
|
||||
var eventRecorder *events.Recorder
|
||||
if eventRecorder, err = events.NewRecorder(mgr, ctrl.Log, eventsAddr, controllerName); err != nil {
|
||||
setupLog.Error(err, "unable to create event recorder")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
metricsH := helper.NewMetrics(mgr, metrics.MustMakeRecorder(), imagev1.ImageUpdateAutomationFinalizer)
|
||||
|
||||
var tokenCache *cache.TokenCache
|
||||
if tokenCacheOptions.MaxSize > 0 {
|
||||
var err error
|
||||
tokenCache, err = cache.NewTokenCache(tokenCacheOptions.MaxSize,
|
||||
cache.WithMaxDuration(tokenCacheOptions.MaxDuration),
|
||||
cache.WithMetricsRegisterer(ctrlmetrics.Registry),
|
||||
cache.WithMetricsPrefix("gotk_token_"))
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to create token cache")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := ctrl.SetupSignalHandler()
|
||||
|
||||
if err := (&controller.ImageUpdateAutomationReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
EventRecorder: eventRecorder,
|
||||
Metrics: metricsH,
|
||||
NoCrossNamespaceRef: aclOptions.NoCrossNamespaceRefs,
|
||||
ControllerName: controllerName,
|
||||
}).SetupWithManager(ctx, mgr, controller.ImageUpdateAutomationReconcilerOptions{
|
||||
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
|
||||
TokenCache: tokenCache,
|
||||
if err = (&controllers.ImageUpdateAutomationReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
EventRecorder: mgr.GetEventRecorderFor(controllerName),
|
||||
ExternalEventRecorder: eventRecorder,
|
||||
MetricsRecorder: metricsRecorder,
|
||||
}).SetupWithManager(mgr, controllers.ImageUpdateAutomationReconcilerOptions{
|
||||
MaxConcurrentReconciles: concurrent,
|
||||
}); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "ImageUpdateAutomation")
|
||||
os.Exit(1)
|
||||
|
@ -254,7 +139,7 @@ func main() {
|
|||
// +kubebuilder:scaffold:builder
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctx); err != nil {
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue