Compare commits

..

No commits in common. "main" and "api/v0.33.0" have entirely different histories.

87 changed files with 3614 additions and 11938 deletions

View File

@ -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"

14
.github/labels.yaml vendored
View File

@ -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'

View File

@ -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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
uses: korthout/backport-action@436145e922f9561fc5ea157ff406f21af2d6b363 # v3.2.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}.

View File

@ -2,7 +2,8 @@ name: build
on:
pull_request:
push:
branches: [ 'main', 'release/**' ]
branches:
- main
permissions:
contents: read # for actions/checkout to fetch code
@ -12,32 +13,66 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@v3
with:
go-version: 1.24.x
cache-dependency-path: |
**/go.sum
**/go.mod
go-version: 1.20.x
- name: Restore go cache
uses: actions/cache@v3
with:
# 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: Run tests
run: make test
- name: Verify
run: make verify
kind-linux-arm64:
runs-on:
group: "ARM64"
# Hosted on Equinix
# Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners
runs-on: [self-hosted, Linux, ARM64, equinix]
steps:
- name: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Checkout
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@v3
with:
go-version: 1.24.x
cache-dependency-path: |
**/go.sum
**/go.mod
go-version: 1.20.x
- name: Run tests
run: make test
env:
# Temporarily disabling -race for arm64 as our GitHub action
# runners don't seem to like it.
#
# We should reenable go test -race for arm64 runners once the
# current issue is resolved.
GO_TEST_ARGS: ""
SKIP_COSIGN_VERIFICATION: true
- name: Verify
run: make verify
# Runs 'make test' on macos-11 to assure development environment for
# contributors using MacOS.
darwin-amd64:
runs-on: macos-11
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.20.x
- name: Restore Go cache
uses: actions/cache@v3
with:
path: /home/runner/work/_temp/_github_home/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run tests
run: make test
env:

View File

@ -1,23 +1,30 @@
name: fuzz
name: CIFuzz
on:
pull_request:
branches: [ 'main', 'release/**' ]
branches:
- main
permissions:
contents: read # for actions/checkout to fetch code
contents: read
jobs:
smoketest:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: 1.24.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Smoke test Fuzzers
run: make fuzz-smoketest
- name: Checkout
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.20.x
- name: Restore Go cache
uses: actions/cache@v3
with:
path: /home/runner/work/_temp/_github_home/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Smoke test Fuzzers
run: make fuzz-smoketest
env:
SKIP_COSIGN_VERIFICATION: true

View File

@ -14,15 +14,16 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@v3
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@v2
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@v2
with:
buildkitd-flags: "--debug"
- name: Build multi-arch container image
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
uses: docker/build-push-action@v4
with:
push: false
builder: ${{ steps.buildx.outputs.name }}

View File

@ -7,29 +7,22 @@ on:
inputs:
tag:
description: 'image tag prefix'
default: 'preview'
default: 'rc'
required: true
permissions:
contents: read
contents: write # needed to write releases
id-token: write # needed for keyless signing
packages: write # needed for ghcr access
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@v3
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Prepare
@ -42,24 +35,24 @@ jobs:
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@v2
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@v2
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@v2
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
- name: Generate images meta
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
uses: docker/metadata-action@v4
with:
images: |
fluxcd/${{ env.CONTROLLER }}
@ -67,8 +60,7 @@ jobs:
tags: |
type=raw,value=${{ steps.prep.outputs.VERSION }}
- name: Publish images
id: build-push
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
uses: docker/build-push-action@v4
with:
sbom: true
provenance: true
@ -79,82 +71,32 @@ jobs:
platforms: linux/amd64,linux/arm/v7,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
- name: Check images
run: |
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 }}
- uses: sigstore/cosign-installer@v3
- name: Sign images
env:
COSIGN_EXPERIMENTAL: 1
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 }}
cosign sign --yes fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
cosign sign --yes ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
- name: Generate release artifacts
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@e11c554f704a0b820cbf8c51673f6945e0731532 # v0.20.0
echo '[CHANGELOG](https://github.com/fluxcd/${{ env.CONTROLLER }}/blob/main/CHANGELOG.md)' > ./config/release/notes.md
- uses: anchore/sbom-action/download-syft@v0
- name: Create release and SBOM
id: run-goreleaser
if: startsWith(github.ref, 'refs/tags/v')
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
uses: goreleaser/goreleaser-action@v4
with:
version: latest
args: release --clean --skip=validate
args: release --release-notes=config/release/notes.md --rm-dist --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 }}

View File

@ -1,10 +1,10 @@
name: scan
name: Scan
on:
push:
branches: [ 'main', 'release/**' ]
branches: [ main ]
pull_request:
branches: [ 'main', 'release/**' ]
branches: [ main ]
schedule:
- cron: '18 10 * * 3'
@ -17,10 +17,9 @@ jobs:
name: FOSSA
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@v3
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1
uses: fossa-contrib/fossa-action@v2
with:
# FOSSA Push-Only API Token
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
@ -30,23 +29,17 @@ jobs:
name: CodeQL
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.24.x
cache-dependency-path: |
**/go.sum
**/go.mod
go-version: 1.20.x
- name: Initialize CodeQL
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/init@v2
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@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/analyze@v2

View File

@ -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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- 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

4
.gitignore vendored
View File

@ -2,7 +2,7 @@ notes
# Thes are downloaded in the Makefile
cache/*
internal/controller/testdata/crds/*
internal/controllers/testdata/crds/*
# Binaries for programs and plugins
*.exe
@ -33,4 +33,4 @@ testbin
build/
# CRDs for fuzzing tests.
internal/controller/testdata/crd
internal/controllers/testdata/crd

View File

@ -4,26 +4,9 @@ builds:
- skip: true
release:
prerelease: "true"
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:

View File

@ -1,537 +1,5 @@
# Changelog
## 0.41.1
**Release date:** 2025-06-13
This patch release comes with a fix for the `knownhosts: key mismatch` error.
Fixes:
- Fix `knownhosts key mismatch` regression bug
[#923](https://github.com/fluxcd/image-automation-controller/pull/923)
- Upgrade dependencies (includes `github.com/go-git/go-git/v5` `v5.16.2`)
[#919](https://github.com/fluxcd/image-automation-controller/pull/919)
## 0.41.0
**Release date:** 2025-05-28
This prerelease comes with support for updating image digests, for
configuring mTLS with HTTPS Git repositories, and with caching Git
provider access tokens.
ImagePolicy API now supports reflecting image digests in-cluster. By
integrating with this feature the ImageUpdateAutomation API can now
update image digests in YAML manifests. See this
[guide](https://fluxcd.io/flux/guides/image-update/#digest-pinning)
for more details.
The controller now caches Git provider access tokens by default.
This behavior can be disabled or fine-tuned by adjusting the
token cache controller flags (see
[docs](https://fluxcd.io/flux/components/image/options/#image-automation-flags)).
The token cache also exposes metrics that are documented
[here](https://fluxcd.io/flux/monitoring/metrics/#controller-metrics).
For configuring mTLS with HTTPS Git repositories see the GitRepository
[docs](https://fluxcd.io/flux/components/source/gitrepositories/#secret-reference).
In addition, the Kubernetes dependencies have been updated to v1.33
and various other controller dependencies have been updated to their latest
version. The controller is now built with Go 1.24.
Fixes:
- Fix tag parsing logic misinterpreting host:port registry url
[#893](https://github.com/fluxcd/image-automation-controller/pull/893)
Improvements:
- Update digest of latest image
[#902](https://github.com/fluxcd/image-automation-controller/pull/902)
- Introduce token cache for Git provider access tokens
[#861](https://github.com/fluxcd/image-automation-controller/pull/861)
[#895](https://github.com/fluxcd/image-automation-controller/pull/895)
[#897](https://github.com/fluxcd/image-automation-controller/pull/897)
- Add support for mutual TLS to Git HTTP/S operations
[#886](https://github.com/fluxcd/image-automation-controller/pull/886)
- Support for all recognized Kustomize config file names
[#864](https://github.com/fluxcd/image-automation-controller/pull/864)
- Various dependency updates
[#916](https://github.com/fluxcd/image-automation-controller/pull/916)
[#908](https://github.com/fluxcd/image-automation-controller/pull/908)
[#900](https://github.com/fluxcd/image-automation-controller/pull/900)
[#896](https://github.com/fluxcd/image-automation-controller/pull/896)
[#898](https://github.com/fluxcd/image-automation-controller/pull/898)
[#891](https://github.com/fluxcd/image-automation-controller/pull/891)
[#890](https://github.com/fluxcd/image-automation-controller/pull/890)
[#881](https://github.com/fluxcd/image-automation-controller/pull/881)
[#880](https://github.com/fluxcd/image-automation-controller/pull/880)
[#878](https://github.com/fluxcd/image-automation-controller/pull/878)
[#856](https://github.com/fluxcd/image-automation-controller/pull/856)
[#854](https://github.com/fluxcd/image-automation-controller/pull/854)
[#852](https://github.com/fluxcd/image-automation-controller/pull/852)
[#850](https://github.com/fluxcd/image-automation-controller/pull/850)
## 0.40.0
**Release date:** 2025-02-14
This prerelease comes with support for GitHub App authentication for GitHub
repositories using the `.spec.provider` field by setting it to `github` on the
`GitRepository` object associated with an `ImageUpdateAutomation` object.
In addition, the Kubernetes dependencies have been updated to v1.32.1
and various other controller dependencies have been updated to their latest
version.
Fixes:
Improvements:
- Additional values for commit message template
[#772](https://github.com/fluxcd/image-automation-controller/pull/772)
- [RFC-007] Implement GitHub app authentication for git repositories in IAC
[#780](https://github.com/fluxcd/image-automation-controller/pull/780)
- Various dependency updates
[#765](https://github.com/fluxcd/image-automation-controller/pull/765)
[#766](https://github.com/fluxcd/image-automation-controller/pull/766)
[#768](https://github.com/fluxcd/image-automation-controller/pull/768)
[#769](https://github.com/fluxcd/image-automation-controller/pull/769)
[#770](https://github.com/fluxcd/image-automation-controller/pull/770)
[#774](https://github.com/fluxcd/image-automation-controller/pull/774)
[#776](https://github.com/fluxcd/image-automation-controller/pull/776)
[#777](https://github.com/fluxcd/image-automation-controller/pull/777)
[#778](https://github.com/fluxcd/image-automation-controller/pull/778)
[#782](https://github.com/fluxcd/image-automation-controller/pull/782)
[#784](https://github.com/fluxcd/image-automation-controller/pull/784)
[#787](https://github.com/fluxcd/image-automation-controller/pull/787)
[#789](https://github.com/fluxcd/image-automation-controller/pull/789)
[#790](https://github.com/fluxcd/image-automation-controller/pull/790)
[#791](https://github.com/fluxcd/image-automation-controller/pull/791)
[#794](https://github.com/fluxcd/image-automation-controller/pull/794)
[#795](https://github.com/fluxcd/image-automation-controller/pull/795)
[#798](https://github.com/fluxcd/image-automation-controller/pull/798)
[#799](https://github.com/fluxcd/image-automation-controller/pull/799)
[#801](https://github.com/fluxcd/image-automation-controller/pull/801)
[#802](https://github.com/fluxcd/image-automation-controller/pull/802)
[#804](https://github.com/fluxcd/image-automation-controller/pull/804)
[#805](https://github.com/fluxcd/image-automation-controller/pull/805)
[#806](https://github.com/fluxcd/image-automation-controller/pull/806)
[#808](https://github.com/fluxcd/image-automation-controller/pull/808)
[#815](https://github.com/fluxcd/image-automation-controller/pull/815)
[#819](https://github.com/fluxcd/image-automation-controller/pull/819)
[#821](https://github.com/fluxcd/image-automation-controller/pull/821)
[#824](https://github.com/fluxcd/image-automation-controller/pull/824)
[#826](https://github.com/fluxcd/image-automation-controller/pull/826)
[#828](https://github.com/fluxcd/image-automation-controller/pull/828)
[#831](https://github.com/fluxcd/image-automation-controller/pull/831)
[#832](https://github.com/fluxcd/image-automation-controller/pull/832)
[#835](https://github.com/fluxcd/image-automation-controller/pull/835)
[#839](https://github.com/fluxcd/image-automation-controller/pull/839)
[#840](https://github.com/fluxcd/image-automation-controller/pull/840)
[#842](https://github.com/fluxcd/image-automation-controller/pull/842)
[#843](https://github.com/fluxcd/image-automation-controller/pull/843)
[#845](https://github.com/fluxcd/image-automation-controller/pull/845)
## 0.39.0
**Release date:** 2024-09-26
This prerelease comes with support for OIDC authentication for Azure DevOps
Repositories using the `.spec.provider` field by setting it to `azure` on the
`GitRepository` object associated with `ImageUpdateAutomation` object.
In addition, the Kubernetes dependencies have been updated to v1.31.1
and various other controller dependencies have been updated to their latest
version. The controller is now built with Go 1.23.
Fixes:
- Fix incorrect use of format strings with the conditions package.
[#711](https://github.com/fluxcd/image-automation-controller/pull/711)
- Fix RBAC role generation for IAC
[#745](https://github.com/fluxcd/image-automation-controller/pull/745)
Improvements:
- [RFC-0007] Enable Azure OIDC for Azure DevOps Repository
[#747](https://github.com/fluxcd/image-automation-controller/pull/747)
- Build with Go 1.23
[#736](https://github.com/fluxcd/image-automation-controller/pull/736)
- Run ARM64 tests on GitHub runners
[#696](https://github.com/fluxcd/image-automation-controller/pull/696)
- Various dependency updates
[#680](https://github.com/fluxcd/image-automation-controller/pull/680)
[#683](https://github.com/fluxcd/image-automation-controller/pull/683)
[#685](https://github.com/fluxcd/image-automation-controller/pull/685)
[#690](https://github.com/fluxcd/image-automation-controller/pull/690)
[#691](https://github.com/fluxcd/image-automation-controller/pull/691)
[#693](https://github.com/fluxcd/image-automation-controller/pull/693)
[#694](https://github.com/fluxcd/image-automation-controller/pull/694)
[#695](https://github.com/fluxcd/image-automation-controller/pull/695)
[#698](https://github.com/fluxcd/image-automation-controller/pull/698)
[#697](https://github.com/fluxcd/image-automation-controller/pull/697)
[#700](https://github.com/fluxcd/image-automation-controller/pull/700)
[#701](https://github.com/fluxcd/image-automation-controller/pull/701)
[#702](https://github.com/fluxcd/image-automation-controller/pull/702)
[#703](https://github.com/fluxcd/image-automation-controller/pull/703)
[#704](https://github.com/fluxcd/image-automation-controller/pull/704)
[#706](https://github.com/fluxcd/image-automation-controller/pull/706)
[#707](https://github.com/fluxcd/image-automation-controller/pull/707)
[#708](https://github.com/fluxcd/image-automation-controller/pull/708)
[#709](https://github.com/fluxcd/image-automation-controller/pull/709)
[#712](https://github.com/fluxcd/image-automation-controller/pull/712)
[#710](https://github.com/fluxcd/image-automation-controller/pull/710)
[#714](https://github.com/fluxcd/image-automation-controller/pull/714)
[#716](https://github.com/fluxcd/image-automation-controller/pull/716)
[#718](https://github.com/fluxcd/image-automation-controller/pull/718)
[#719](https://github.com/fluxcd/image-automation-controller/pull/719)
[#720](https://github.com/fluxcd/image-automation-controller/pull/720)
[#724](https://github.com/fluxcd/image-automation-controller/pull/724)
[#722](https://github.com/fluxcd/image-automation-controller/pull/722)
[#727](https://github.com/fluxcd/image-automation-controller/pull/727)
[#726](https://github.com/fluxcd/image-automation-controller/pull/726)
[#728](https://github.com/fluxcd/image-automation-controller/pull/728)
[#729](https://github.com/fluxcd/image-automation-controller/pull/729)
[#730](https://github.com/fluxcd/image-automation-controller/pull/730)
[#731](https://github.com/fluxcd/image-automation-controller/pull/731)
[#732](https://github.com/fluxcd/image-automation-controller/pull/732)
[#734](https://github.com/fluxcd/image-automation-controller/pull/734)
[#733](https://github.com/fluxcd/image-automation-controller/pull/733)
[#737](https://github.com/fluxcd/image-automation-controller/pull/737)
[#738](https://github.com/fluxcd/image-automation-controller/pull/738)
[#739](https://github.com/fluxcd/image-automation-controller/pull/739)
[#740](https://github.com/fluxcd/image-automation-controller/pull/740)
[#741](https://github.com/fluxcd/image-automation-controller/pull/741)
[#742](https://github.com/fluxcd/image-automation-controller/pull/742)
[#743](https://github.com/fluxcd/image-automation-controller/pull/743)
[#748](https://github.com/fluxcd/image-automation-controller/pull/748)
[#750](https://github.com/fluxcd/image-automation-controller/pull/750)
[#752](https://github.com/fluxcd/image-automation-controller/pull/752)
[#755](https://github.com/fluxcd/image-automation-controller/pull/755)
[#757](https://github.com/fluxcd/image-automation-controller/pull/757)
[#759](https://github.com/fluxcd/image-automation-controller/pull/759)
## 0.38.0
**Release date:** 2024-05-06
This prerelease graduates the `ImageUpdateAutomation` API to v1beta2.
### `image.toolkit.fluxcd.io/v1beta2`
After upgrading the controller to v0.38.0, please update the
`ImageUpdateAutomation` **Custom Resources** in Git by replacing
`image.toolkit.fluxcd.io/v1beta1` with `image.toolkit.fluxcd.io/v1beta2` in all
YAML manifests. Bumping the API version in manifests can be done gradually. It
is advised not to delay this procedure as the `v1beta1` version will be removed
after 6 months.
### Highlights
#### New API specification format
[The specification for the `v1beta2`
API](https://github.com/fluxcd/image-automation-controller/tree/v0.38.0/docs/spec/v1beta2)
has been written in a new format with the aim to be more valuable to a user.
Featuring separate sections with examples, and information on how to write and
work with them.
#### New template data `Changed` and deprecation of `Updated`
A new Git commit message template data named `Changed` is introduced to replace
`Updated` template data. `Changed` is designed to accommodate for all the types
of updates made by ImaegUpdateAutomation, unlike only full image reference
updates captured by `Updated`. The message template can now be used to render
the old and new values for better presentation of the updates. For example:
```
Automation: default/test-update-auto
- File: foo-deployment.yaml
- Object: Deployment/default/foo
Changes:
- 2.2.2 -> 5.0.3
- File: podinfo-deployment.yaml
- Object: Deployment/default/infopod
Changes:
- v1.0 -> 5.0.3
- Object: Deployment/default/podinfo
Changes:
- ghcr.io/stefanprodan/podinfo:4.0.6 -> ghcr.io/stefanprodan/podinfo:5.0.3
- bar -> ghcr.io/stefanprodan/podinfo
- 4.0.6 -> 5.0.3
```
`Updated` template data is deprecated, but is still available in the message
template, for existing users. It is recommended to migrate to `Changed` template
data. See the new API specification docs for details about the new template
data.
#### ImagePolicy selector support
`ImageUpdateAutomation` now supports selecting `ImagePolicies` using label
selectors in the new field `.spec.policySelector`. For example:
```yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
name: update-app
spec:
policySelector:
matchLabels:
app.kubernetes.io/component: foo
app.kubernetes.io/instance: bar
...
```
See the new API specification docs for details and more examples.
#### short-circuit reconciliations
For same push branch and checkout branch, the controller now checks with the
remote Git repository if there's any new commit and the image policies for any
new latest image before performing full reconciliation, otherwise the
reconciliation returns early. This helps avoid cloning the Git repository every
reconciliation even when there is no new change.
#### Enhancements in events and notifications
The events and notifications have been improved to provide better information.
Notifications are sent for: initial successful reconciliation, update
pushes, failures and successful failure recovery.
In addition, the controller is now built with Go 1.22, the Kubernetes
dependencies have been updated to v1.30.0, and various other dependencies have
been updated to their latest version.
### Full changelog
Improvements:
- Deprecate v1beta1 API
[#677](https://github.com/fluxcd/image-automation-controller/pull/677)
- Update source-controller API to v1.3.0
[#676](https://github.com/fluxcd/image-automation-controller/pull/676)
- Update dependencies to Kubernetes 1.30
[#670](https://github.com/fluxcd/image-automation-controller/pull/670)
- ImageUpdateAutomation v1beta2 API with refactored controller
[#647](https://github.com/fluxcd/image-automation-controller/pull/647)
- Update dependencies to Kustomize v5.4.0
[#662](https://github.com/fluxcd/image-automation-controller/pull/662)
- Update dependencies to Go 1.22 and Kubernetes 1.29.3
[#661](https://github.com/fluxcd/image-automation-controller/pull/661)
- Add tests for getExtFromSchema
[#658](https://github.com/fluxcd/image-automation-controller/pull/658)
- Introduce ResultV2 for update results
[#642](https://github.com/fluxcd/image-automation-controller/pull/642)
- updating controller-gen to v0.14.0
[#649](https://github.com/fluxcd/image-automation-controller/pull/649)
- Add predicates for GitRepo and ImagePolicy watches
[#639](https://github.com/fluxcd/image-automation-controller/pull/639)
- adding tests for update accept function
[#636](https://github.com/fluxcd/image-automation-controller/pull/636)
- Various dependency updates
[#637](https://github.com/fluxcd/image-automation-controller/pull/637)
[#652](https://github.com/fluxcd/image-automation-controller/pull/652)
[#660](https://github.com/fluxcd/image-automation-controller/pull/660)
[#668](https://github.com/fluxcd/image-automation-controller/pull/668)
[#665](https://github.com/fluxcd/image-automation-controller/pull/665)
[#666](https://github.com/fluxcd/image-automation-controller/pull/666)
[#673](https://github.com/fluxcd/image-automation-controller/pull/673)
## 0.37.1
**Release date:** 2024-02-01
This prerelease comes with an update to the Kubernetes dependencies to
v1.28.6 and various other dependencies have been updated to their latest version
to patch upstream CVEs.
In addition, the controller is now built with Go 1.21.
Improvements:
- ci: Enable dependabot gomod updates
[#627](https://github.com/fluxcd/image-automation-controller/pull/627)
- Update Go to 1.21
[#625](https://github.com/fluxcd/image-automation-controller/pull/625)
- Various dependency updates
[#635](https://github.com/fluxcd/image-automation-controller/pull/635)
[#632](https://github.com/fluxcd/image-automation-controller/pull/632)
[#630](https://github.com/fluxcd/image-automation-controller/pull/630)
[#631](https://github.com/fluxcd/image-automation-controller/pull/631)
[#629](https://github.com/fluxcd/image-automation-controller/pull/629)
[#626](https://github.com/fluxcd/image-automation-controller/pull/626)
[#623](https://github.com/fluxcd/image-automation-controller/pull/623)
[#622](https://github.com/fluxcd/image-automation-controller/pull/622)
[#618](https://github.com/fluxcd/image-automation-controller/pull/618)
[#615](https://github.com/fluxcd/image-automation-controller/pull/615)
## 0.37.0
**Release date:** 2023-12-11
This prerelease comes with updates to the controller's YAML parser and fixes
all the issues with YAML multi-line strings being flattened.
In addition, the controller dependencies have been updated to Kubernetes 1.28
and the container base image has been updated to Alpine 3.19.
Improvements:
- Remove dependence on `kustomize/setters2`
[#604](https://github.com/fluxcd/image-automation-controller/pull/604)
- Update dependencies to Kubernetes v1.28
[#605](https://github.com/fluxcd/image-automation-controller/pull/605)
- Update Git dependencies
[#607](https://github.com/fluxcd/image-automation-controller/pull/607)
- build: update Alpine to 3.19
[#610](https://github.com/fluxcd/image-automation-controller/pull/610)
- Update Kustomize to v5.3.0
[#611](https://github.com/fluxcd/image-automation-controller/pull/611)
- Update dependencies
[#613](https://github.com/fluxcd/image-automation-controller/pull/613)
## 0.36.1
**Release date:** 2023-09-18
This prerelease fixes the push branch reported in the logs and status under
certain circumstances.
It also upgrades the version of github.com/cyphar/filepath-securejoin that fixes
[GHSA-6xv5-86q9-7xr8](https://github.com/advisories/GHSA-6xv5-86q9-7xr8).
Even though the Flux controllers are not affected by this vulnerability
since they don't run on Windows nodes, this bump will keep security scanners silent.
Fixes:
- Fix bad link in docs
[#578](https://github.com/fluxcd/image-automation-controller/pull/578)
- bump github.com/cyphar/filepath-securejoin
[#584](https://github.com/fluxcd/image-automation-controller/pull/584)
- Fix push branch reporting when is equal to checkout branch
[#581](https://github.com/fluxcd/image-automation-controller/pull/581)
- Upgrade github.com/fluxcd/pkg/{git,git/gogit}
[#586](https://github.com/fluxcd/image-automation-controller/pull/586)
## 0.36.0
**Release date:** 2023-08-23
This prerelease introduces a new field `.spec.git.push.refspec` to the
ImageUpdateAutomation API. This field can be used to make the controller push
the commits it makes using a specific refspec. It also improves compatibility
with Gerrit. For more details, check the [documentation](https://github.com/fluxcd/image-automation-controller/blob/v0.36.0/docs/spec/v1beta1/imageupdateautomations.md#gerrit).
Additionally, a new field `.spec.git.push.options` has been added to the
ImageUpdateAutomation API for specifying the [push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt)
to be sent to the Git server when performing a push operation.
From this release on, the controller also stops exporting an object's metrics
as soon as the object has been deleted.
Lastly, it adds support for using the referred GitRepository's
`.spec.proxySecretRef` field for all remote Git operations related to the
object.
Fixes:
- Fix links in API docs
[#573](https://github.com/fluxcd/image-automation-controller/pull/573)
Improvements:
- git: add push.refspec to push using a refspec
[#514](https://github.com/fluxcd/image-automation-controller/pull/514)
- Handle delete before adding finalizer
[#564](https://github.com/fluxcd/image-automation-controller/pull/564)
- add support for specified proxy in source GitRepository
[#565](https://github.com/fluxcd/image-automation-controller/pull/565)
- Delete stale metrics on object delete
[#570](https://github.com/fluxcd/image-automation-controller/pull/570)
- Update dependencies
[#571](https://github.com/fluxcd/image-automation-controller/pull/571)
- add support for specifying push options
[#577](https://github.com/fluxcd/image-automation-controller/pull/577)
## 0.35.0
**Release date:** 2023-07-04
This prerelease comes with support for Kubernetes v1.27.3 and updates to the
controller's dependencies.
It has better error reporting for situations where the Git repository deploy key
does not have write acceess. Additionally, it improves support for
Git >= v2.41.0.
Furthermore, a bug related to GPG commit signing that could cause panics has
been fixed.
Starting with this version, the build, release and provenance portions of the
Flux project supply chain [provisionally meet SLSA Build Level 3](https://fluxcd.io/flux/security/slsa-assessment/).
Fixes:
- Check if GPG private key is nil before decrypting
[#534](https://github.com/fluxcd/image-automation-controller/pull/534)
Improvements:
- Align `go.mod` version with Kubernetes (Go 1.20)
[#545](https://github.com/fluxcd/image-automation-controller/pull/545)
- Update Git packages
[#549](https://github.com/fluxcd/image-automation-controller/pull/549)
- Update go-git to unreleased v5.8.0
[#553](https://github.com/fluxcd/image-automation-controller/pull/553)
- Update Go dependencies
[#554](https://github.com/fluxcd/image-automation-controller/pull/554)
## 0.34.1
**Release date:** 2023-06-01
This prerelease fixes a regression introduced in `v0.34.0` where
support for Git servers that exclusively use v2 of the wire protocol like Azure
Devops and AWS CodeCommit was broken.
Furthermore, the reconciler now errors out if it fails to get the signing entity
to be used for Git commit signing.
Fixes:
- Return signing entity parsing error
[#527](https://github.com/fluxcd/image-automation-controller/pull/527)
- Set controller package name
[#529](https://github.com/fluxcd/image-automation-controller/pull/529)
- Bump `fluxcd/pkg/git/gogit` to v0.12.0
[#530](https://github.com/fluxcd/image-automation-controller/pull/530)
## 0.34.0
**Release date:** 2023-05-29
This prerelease comes with support for Kubernetes v1.27 and updates to the
controller's dependencies.
Improvements:
- Update controller-runtime, Kubernetes and kyaml dependencies
[#518](https://github.com/fluxcd/image-automation-controller/pull/518)
- Drop go-git fork in favor of go-git v5.7.0
[#519](https://github.com/fluxcd/image-automation-controller/pull/519)
- Update workflows and enable dependabot
[#520](https://github.com/fluxcd/image-automation-controller/pull/520)
- build(deps): bump github/codeql-action from 2.3.3 to 2.3.4
[#521](https://github.com/fluxcd/image-automation-controller/pull/521)
- Update source-controller to v1.0.0-rc.4
[#523](https://github.com/fluxcd/image-automation-controller/pull/523)
## 0.33.1
**Release date:** 2023-05-12
This prerelease comes with updates to the controller dependencies
to patch CVE-2023-1732.
In addition, the controller base image has been updated to Alpine 3.18.
Improvements:
- Update Alpine to 3.18
[#513](https://github.com/fluxcd/image-automation-controller/pull/513)
- Update dependencies
[#516](https://github.com/fluxcd/image-automation-controller/pull/516)
- build(deps): bump github.com/cloudflare/circl from 1.3.2 to 1.3.3
[#515](https://github.com/fluxcd/image-automation-controller/pull/515)
## 0.33.0
**Release date:** 2023-05-09

View File

@ -24,7 +24,7 @@ If any of the above dependencies are not present on your system, the first invoc
## How to run the test suite
Prerequisites:
* Go >= 1.24
* Go >= 1.18
You can run the test suite by simply doing

View File

@ -1,10 +1,10 @@
ARG BASE_VARIANT=alpine
ARG GO_VERSION=1.24
ARG XX_VERSION=1.6.1
ARG GO_VERSION=1.20
ARG XX_VERSION=1.2.1
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${BASE_VARIANT} AS gostable
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${BASE_VARIANT} as gostable
FROM gostable AS go-linux
@ -17,7 +17,7 @@ RUN apk add clang lld
COPY --from=xx / /
# build can still be cached at build platform architecture.
FROM build-base AS build
FROM build-base as build
ARG TARGETPLATFORM
@ -60,7 +60,7 @@ RUN export CGO_LDFLAGS="-static -fuse-ld=lld" && \
# Ensure that the binary was cross-compiled correctly to the target platform.
RUN xx-verify --static /image-automation-controller
FROM alpine:3.21
FROM alpine:3.17
ARG TARGETPLATFORM
RUN apk --no-cache add ca-certificates \

View File

@ -9,7 +9,6 @@ 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:

View File

@ -46,7 +46,7 @@ ifeq ($(shell uname -s),Linux)
endif
# API (doc) generation utilities
CONTROLLER_GEN_VERSION ?= v0.16.1
CONTROLLER_GEN_VERSION ?= v0.11.1
GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113
# If gobin not set, create one on ./build and add to path.
@ -69,7 +69,7 @@ ifeq ($(shell uname -s),Darwin)
ENVTEST_ARCH=amd64
endif
TEST_CRDS := internal/controller/testdata/crds
TEST_CRDS := internal/controllers/testdata/crds
# Log level for `make run`
LOG_LEVEL ?= info
@ -139,15 +139,14 @@ dev-deploy: manifests
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"
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
$(GEN_CRD_API_REFERENCE_DOCS) -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.24
rm -f go.sum; go mod tidy -compat=1.24
cd api; rm -f go.sum; go mod tidy -compat=1.20
rm -f go.sum; go mod tidy -compat=1.20
fmt: ## Run go fmt against code
go fmt ./...

View File

@ -4,7 +4,4 @@ resources:
- group: image
kind: ImageUpdateAutomation
version: v1beta1
- group: image
kind: ImageUpdateAutomation
version: v1beta2
version: "2"

View File

@ -1,32 +1,27 @@
module github.com/fluxcd/image-automation-controller/api
go 1.24.0
go 1.18
require (
github.com/fluxcd/pkg/apis/meta v1.12.0
github.com/fluxcd/source-controller/api v1.6.1
k8s.io/apimachinery v0.33.0
sigs.k8s.io/controller-runtime v0.21.0
github.com/fluxcd/pkg/apis/meta v1.0.0
github.com/fluxcd/source-controller/api v1.0.0-rc.2
k8s.io/apimachinery v0.26.3
sigs.k8s.io/controller-runtime v0.14.6
)
require (
github.com/fluxcd/pkg/apis/acl v0.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gofuzz v1.2.0 // 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.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/text v0.7.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
)

View File

@ -1,57 +1,38 @@
github.com/creack/pty v1.1.9/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.12.0 h1:XW15TKZieC2b7MN8VS85stqZJOx+/b8jATQ/xTUhVYg=
github.com/fluxcd/pkg/apis/meta v1.12.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI=
github.com/fluxcd/source-controller/api v1.6.1 h1:ZPTA9lNzBYHmwHfFX978qb8xVkdnQZHF1ggo6BoFm4w=
github.com/fluxcd/source-controller/api v1.6.1/go.mod h1:ZJcAi0nemsnBxjVgmJl0WQzNvB0rMETxQMTdoFosmMw=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/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/fluxcd/pkg/apis/meta v1.0.0 h1:i9IGHd/VNEZELX7mepkiYFbJxs2J5znaB4cN9z2nPm8=
github.com/fluxcd/pkg/apis/meta v1.0.0/go.mod h1:04ZdpZYm1x+aL93K4daNHW1UX6E8K7Gyf5za9OhrE+U=
github.com/fluxcd/source-controller/api v1.0.0-rc.2 h1:14S47wfNrN92KqLkpbfSgWr84ALhogbFgaRapit/9XI=
github.com/fluxcd/source-controller/api v1.0.0-rc.2/go.mod h1:CvGNdS8g/MqwpERUK6aJp4lndsrm+JBzGpoyyZ4u0c8=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
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.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
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/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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/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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc=
github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E=
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.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -63,53 +44,46 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
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.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
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-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
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/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k=
k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y=
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA=
sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=

View File

@ -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"`
}

View File

@ -133,9 +133,9 @@ func SetImageUpdateAutomationReadiness(auto *ImageUpdateAutomation, status metav
apimeta.SetStatusCondition(auto.GetStatusConditions(), newCondition)
}
//+kubebuilder:storageversion
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:deprecatedversion:warning="v1beta1 ImageUpdateAutomation is deprecated, upgrade to v1beta2"
//+kubebuilder:printcolumn:name="Last run",type=string,JSONPath=`.status.lastAutomationRunTime`
// ImageUpdateAutomation is the Schema for the imageupdateautomations API

View File

@ -1,7 +1,8 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
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.
@ -104,7 +105,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 +239,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.

View File

@ -1,39 +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"
)

View File

@ -1,24 +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 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.
//
// +kubebuilder:object:generate=true
// +groupName=image.toolkit.fluxcd.io
package v1beta2

View File

@ -1,117 +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 (
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
)
type GitSpec struct {
// 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.
// +optional
Checkout *GitCheckoutSpec `json:"checkout,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` or its default.
// +optional
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.
// +required
Reference sourcev1.GitRepositoryRef `json:"ref"`
}
// CommitSpec specifies how to commit changes to the git repository
type CommitSpec struct {
// Author gives the email and optionally the name to use as the
// author of commits.
// +required
Author CommitUser `json:"author"`
// 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"`
// MessageTemplateValues provides additional values to be available to the
// templating rendering.
// +optional
MessageTemplateValues map[string]string `json:"messageTemplateValues,omitempty"`
}
type CommitUser struct {
// Name gives the name to provide when making a commit.
// +optional
Name string `json:"name,omitempty"`
// Email gives the email to provide when making a commit.
// +required
Email string `json:"email"`
}
// 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"`
}
// 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.
// +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"`
}

View File

@ -1,36 +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 contains API Schema definitions for the image v1beta2 API group
// +kubebuilder:object:generate=true
// +groupName=image.toolkit.fluxcd.io
package v1beta2
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: "v1beta2"}
// 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
)

View File

@ -1,174 +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 (
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/apis/meta"
)
const (
ImageUpdateAutomationKind = "ImageUpdateAutomation"
ImageUpdateAutomationFinalizer = "finalizers.fluxcd.io"
)
// 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"`
// GitSpec contains all the git-specific definitions. This is
// technically optional, but in practice mandatory until there are
// other kinds of source allowed.
// +optional
GitSpec *GitSpec `json:"git,omitempty"`
// 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.
// +kubebuilder:default={"strategy":"Setters"}
Update *UpdateStrategy `json:"update,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"`
}
// 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"`
}
// 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"`
// 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"`
meta.ReconcileRequestStatus `json:",inline"`
}
// ObservedPolicies is a map of policy name and ImageRef of their latest
// ImageRef.
type ObservedPolicies map[string]ImageRef
//+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}
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
}
//+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{})
}

View File

@ -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
}

View File

@ -1,344 +0,0 @@
//go:build !ignore_autogenerated
/*
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.
*/
// Code generated by controller-gen. DO NOT EDIT.
package v1beta2
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
out.Author = in.Author
if in.SigningKey != nil {
in, out := &in.SigningKey, &out.SigningKey
*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.
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 *CommitUser) DeepCopyInto(out *CommitUser) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommitUser.
func (in *CommitUser) DeepCopy() *CommitUser {
if in == nil {
return nil
}
out := new(CommitUser)
in.DeepCopyInto(out)
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
out.Reference = in.Reference
}
// 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 *GitSpec) DeepCopyInto(out *GitSpec) {
*out = *in
if in.Checkout != nil {
in, out := &in.Checkout, &out.Checkout
*out = new(GitCheckoutSpec)
**out = **in
}
in.Commit.DeepCopyInto(&out.Commit)
if in.Push != nil {
in, out := &in.Push, &out.Push
*out = new(PushSpec)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitSpec.
func (in *GitSpec) DeepCopy() *GitSpec {
if in == nil {
return nil
}
out := new(GitSpec)
in.DeepCopyInto(out)
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
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.SourceRef = in.SourceRef
if in.GitSpec != nil {
in, out := &in.GitSpec, &out.GitSpec
*out = new(GitSpec)
(*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)
**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])
}
}
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
}
// 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 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.
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
}

View File

@ -3,7 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.16.1
controller-gen.kubebuilder.io/version: v0.11.1
creationTimestamp: null
name: imageupdateautomations.image.toolkit.fluxcd.io
spec:
group: image.toolkit.fluxcd.io
@ -18,8 +19,6 @@ spec:
- jsonPath: .status.lastAutomationRunTime
name: Last run
type: string
deprecated: true
deprecationWarning: v1beta1 ImageUpdateAutomation is deprecated, upgrade to v1beta2
name: v1beta1
schema:
openAPIV3Schema:
@ -27,19 +26,14 @@ spec:
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
@ -47,39 +41,36 @@ 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
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.
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.
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.
description: "Commit SHA to check out, takes precedence
over all reference fields. \n This can be combined with
Branch to shallow clone the branch, in which the commit
is expected to exist."
type: string
name:
description: |-
Name of the reference to check out; takes precedence over Branch, Tag and SemVer.
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"
description: "Name of the reference to check out; takes
precedence over Branch, Tag and SemVer. \n It must be
a valid Git reference: https://git-scm.com/docs/git-check-ref-format#_description
Examples: \"refs/heads/main\", \"refs/tags/v0.1.0\",
\"refs/pull/420/head\", \"refs/merge-requests/1/head\""
type: string
semver:
description: SemVer tag expression to check out, takes
@ -96,9 +87,8 @@ spec:
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.
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
@ -112,20 +102,19 @@ spec:
- email
type: object
messageTemplate:
description: |-
MessageTemplate provides a template for the commit message,
into which will be interpolated the details of the change made.
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.
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.
@ -133,53 +122,33 @@ spec:
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
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
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
description: Interval gives an lower bound for how often the automation
run should be attempted.
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
type: string
sourceRef:
description: |-
SourceRef refers to the resource giving access details
description: SourceRef refers to the resource giving access details
to a git repository.
properties:
apiVersion:
@ -203,23 +172,20 @@ spec:
- 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.
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
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
@ -242,35 +208,43 @@ spec:
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,
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
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.
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
@ -285,6 +259,10 @@ spec:
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
@ -297,22 +275,19 @@ 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
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 of the annotation value 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.
@ -324,399 +299,6 @@ spec:
type: object
type: object
served: true
storage: false
subresources:
status: {}
- additionalPrinterColumns:
- jsonPath: .status.lastAutomationRunTime
name: Last run
type: string
name: v1beta2
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:
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
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
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
type: object
required:
- commit
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))+$
type: string
policySelector:
description: |-
PolicySelector allows to filter applied policies based on labels.
By default includes all policies in namespace.
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.
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:
default:
observedGeneration: -1
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.
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.
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 of the annotation value
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
observedPolicies:
additionalProperties:
description: ImageRef represents an image reference.
properties:
digest:
description: Digest is the image's digest.
type: string
name:
description: Name is the bare image's name.
type: string
tag:
description: Tag is the image's tag.
type: string
required:
- name
- tag
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: string
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@ -5,4 +5,4 @@ resources:
images:
- name: fluxcd/image-automation-controller
newName: fluxcd/image-automation-controller
newTag: v0.41.1
newTag: v0.33.0

View File

@ -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:

View File

@ -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

View File

@ -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>
@ -638,42 +638,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&rsquo;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&mdash;push-optionltoptiongt</a></p>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -1,907 +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.</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&rsquo;s name.</p>
</td>
</tr>
<tr>
<td>
<code>tag</code><br>
<em>
string
</em>
</td>
<td>
<p>Tag is the image&rsquo;s tag.</p>
</td>
</tr>
<tr>
<td>
<code>digest</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Digest is the image&rsquo;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:&quot;observedPolicies,omitempty&quot;</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&rsquo;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&mdash;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 &lsquo;git.asc&rsquo; 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>
<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 &lsquo;None&rsquo;, 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>

View File

@ -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.
@ -266,7 +264,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.
@ -398,7 +396,6 @@ spec:
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
The optional `push` field defines how commits are pushed to the origin.
@ -409,34 +406,17 @@ 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`,
@ -445,16 +425,6 @@ Note that without force push in push branches, if the target branch is stale, th
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.
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
pushed:
@ -469,117 +439,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
@ -831,7 +690,7 @@ spec:
```
[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
[git-repo-ref]: https://fluxcd.io/flux/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://fluxcd.io/flux/components/source/gitrepositories/#git-implementation
[go-text-template]: https://golang.org/pkg/text/template/

File diff suppressed because it is too large Load Diff

251
go.mod
View File

@ -1,180 +1,149 @@
module github.com/fluxcd/image-automation-controller
go 1.24.0
go 1.18
replace github.com/fluxcd/image-automation-controller/api => ./api
// Pin kustomize to v5.6.0
replace (
sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.19.0
sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.19.0
require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1
github.com/Masterminds/sprig/v3 v3.2.3
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756
github.com/cyphar/filepath-securejoin v0.2.3
github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4
github.com/fluxcd/image-automation-controller/api v0.33.0
github.com/fluxcd/image-reflector-controller/api v0.27.1
github.com/fluxcd/pkg/apis/acl v0.1.0
github.com/fluxcd/pkg/apis/event v0.4.1
github.com/fluxcd/pkg/apis/meta v1.0.0
github.com/fluxcd/pkg/git v0.11.0
github.com/fluxcd/pkg/git/gogit v0.8.1
github.com/fluxcd/pkg/gittestserver v0.8.2
github.com/fluxcd/pkg/runtime v0.35.0
github.com/fluxcd/pkg/ssh v0.7.3
github.com/fluxcd/source-controller/api v1.0.0-rc.2
github.com/go-git/go-billy/v5 v5.4.1
github.com/go-logr/logr v1.2.4
github.com/google/go-containerregistry v0.15.1
github.com/onsi/gomega v1.27.6
github.com/otiai10/copy v1.11.0
github.com/spf13/pflag v1.0.5
k8s.io/api v0.26.3
k8s.io/apimachinery v0.26.3
k8s.io/client-go v0.26.3
k8s.io/kube-openapi v0.0.0-20230327201221-f5883ff37f0c
sigs.k8s.io/controller-runtime v0.14.6
sigs.k8s.io/kustomize/kyaml v0.13.9
)
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.2.0
github.com/cyphar/filepath-securejoin v0.4.1
github.com/fluxcd/image-automation-controller/api v0.41.1
github.com/fluxcd/image-reflector-controller/api v0.35.2
github.com/fluxcd/pkg/apis/acl v0.7.0
github.com/fluxcd/pkg/apis/event v0.17.0
github.com/fluxcd/pkg/apis/meta v1.12.0
github.com/fluxcd/pkg/auth v0.17.0
github.com/fluxcd/pkg/cache v0.9.0
github.com/fluxcd/pkg/git v0.32.0
github.com/fluxcd/pkg/git/gogit v0.35.0
github.com/fluxcd/pkg/gittestserver v0.17.0
github.com/fluxcd/pkg/runtime v0.60.0
github.com/fluxcd/pkg/ssh v0.19.0
github.com/fluxcd/source-controller/api v1.6.1
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.2
github.com/google/go-containerregistry v0.20.3
github.com/onsi/gomega v1.37.0
github.com/otiai10/copy v1.14.1
github.com/spf13/pflag v1.0.6
k8s.io/api v0.33.0
k8s.io/apimachinery v0.33.0
k8s.io/client-go v0.33.0
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e
sigs.k8s.io/controller-runtime v0.21.0
sigs.k8s.io/kustomize/kyaml v0.19.0
)
require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // 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/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.43.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.33.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/bradleyfalzon/ghinstallation/v2 v2.15.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 v27.5.0+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cloudflare/circl v1.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.10.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fluxcd/cli-utils v0.36.0-flux.13 // indirect
github.com/fluxcd/gitkit v0.6.0 // indirect
github.com/fluxcd/pkg/version v0.7.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.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/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/fluxcd/pkg/version v0.2.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-logr/zapr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.1 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gofrs/uuid v4.2.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.2.2 // 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.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-github/v71 v71.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/google/uuid v1.3.0 // 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.7 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/hashicorp/go-hclog v1.3.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.15 // 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/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // 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/mapstructure v1.5.0 // 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/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/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.3.2 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.16.1 // 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/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/russross/blackfriday v1.6.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.11.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/oauth2 v0.7.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.8.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.33.0 // indirect
k8s.io/cli-runtime v0.33.0 // indirect
k8s.io/component-base v0.33.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kubectl v0.33.0 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/kustomize/api v0.19.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
k8s.io/apiextensions-apiserver v0.26.1 // indirect
k8s.io/cli-runtime v0.25.4 // indirect
k8s.io/component-base v0.26.3 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kubectl v0.25.4 // indirect
k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 // indirect
sigs.k8s.io/cli-utils v0.34.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

1086
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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.

View File

@ -1,642 +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"
imagev1_reflect "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
return []string{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(
&imagev1_reflect.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
if err := r.List(ctx, &autoList, client.InNamespace(obj.GetNamespace()),
client.MatchingFields{repoRefKey: obj.GetName()}); 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 {
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) ([]imagev1_reflect.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 imagev1_reflect.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 := []imagev1_reflect.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

View File

@ -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"
imagev1_reflect "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.(*imagev1_reflect.ImagePolicy)
if !ok {
return false
}
newSource, ok := e.ObjectNew.(*imagev1_reflect.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()
}

View File

@ -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"
imagev1_reflect "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 *imagev1_reflect.ImagePolicy)
want bool
}{
{
name: "no latest image",
beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) {
oldObj.Status.LatestRef = nil
newObj.Status.LatestRef = nil
},
want: false,
},
{
name: "new image, no old image",
beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) {
oldObj.Status.LatestRef = nil
newObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "foo"}
},
want: true,
},
{
name: "different old and new image",
beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) {
oldObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "bar"}
newObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "foo"}
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
oldObj := &imagev1_reflect.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))
})
}
}

View File

@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package controllers
import (
"context"
@ -29,16 +29,16 @@ import (
"os/exec"
"path/filepath"
"sync"
"testing"
"time"
fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/fluxcd/go-git/v5"
gogit "github.com/fluxcd/go-git/v5"
"github.com/fluxcd/go-git/v5/config"
"github.com/fluxcd/go-git/v5/plumbing"
"github.com/fluxcd/go-git/v5/plumbing/object"
"github.com/fluxcd/go-git/v5/storage/memory"
"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"
@ -57,7 +57,7 @@ import (
"github.com/fluxcd/pkg/runtime/testenv"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
image_automationv1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
image_automationv1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
"github.com/fluxcd/image-automation-controller/pkg/update"
)
@ -85,8 +85,9 @@ func Fuzz_ImageUpdateReconciler(f *testing.F) {
utilruntime.Must(ensureDependencies(func(m manager.Manager) {
utilruntime.Must((&ImageUpdateAutomationReconciler{
Client: m.GetClient(),
}).SetupWithManager(context.TODO(), m, ImageUpdateAutomationReconcilerOptions{
RateLimiter: controller.GetDefaultRateLimiter(),
}).SetupWithManager(m, ImageUpdateAutomationReconcilerOptions{
MaxConcurrentReconciles: 4,
RateLimiter: controller.GetDefaultRateLimiter(),
}))
}))
})
@ -333,7 +334,7 @@ func FuzzUpdateWithSetters(f *testing.F) {
// 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)
repo, err := git.Init(memory.NewStorage(), fs)
if err != nil {
return err
}
@ -347,7 +348,7 @@ func initGitRepo(gitServer *gittestserver.GitServer, fixture, branch, repository
if err != nil {
return err
}
if err = working.Checkout(&gogit.CheckoutOptions{
if err = working.Checkout(&git.CheckoutOptions{
Branch: plumbing.NewBranchReferenceName(branch),
Create: true,
}); err != nil {
@ -362,7 +363,7 @@ func initGitRepo(gitServer *gittestserver.GitServer, fixture, branch, repository
return err
}
return remote.Push(&gogit.PushOptions{
return remote.Push(&git.PushOptions{
RefSpecs: []config.RefSpec{
config.RefSpec(fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)),
},

View File

@ -0,0 +1,645 @@
/*
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"
"math"
"net/url"
"os"
"strings"
"text/template"
"time"
"github.com/Masterminds/sprig/v3"
"github.com/ProtonMail/go-crypto/openpgp"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
kuberecorder "k8s.io/client-go/tools/record"
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/ratelimiter"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
apiacl "github.com/fluxcd/pkg/apis/acl"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit"
"github.com/fluxcd/pkg/git/repository"
"github.com/fluxcd/pkg/runtime/acl"
helper "github.com/fluxcd/pkg/runtime/controller"
"github.com/fluxcd/pkg/runtime/logger"
"github.com/fluxcd/pkg/runtime/predicates"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
extgogit "github.com/fluxcd/go-git/v5"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
"github.com/fluxcd/image-automation-controller/internal/features"
"github.com/fluxcd/image-automation-controller/pkg/update"
)
const (
originRemote = "origin"
defaultMessageTemplate = `Update from image update automation`
repoRefKey = ".spec.gitRepository"
signingSecretKey = "git.asc"
signingPassphraseKey = "passphrase"
)
// 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
EventRecorder kuberecorder.EventRecorder
helper.Metrics
NoCrossNamespaceRef bool
features map[string]bool
}
type ImageUpdateAutomationReconcilerOptions struct {
MaxConcurrentReconciles int
RateLimiter ratelimiter.RateLimiter
RecoverPanic bool
}
// +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 := ctrl.LoggerFrom(ctx)
debuglog := log.V(logger.DebugLevel)
tracelog := log.V(logger.TraceLevel)
start := 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)
}
// Add our finalizer if it does not exist.
if !controllerutil.ContainsFinalizer(&auto, imagev1.ImageUpdateAutomationFinalizer) {
patch := client.MergeFrom(auto.DeepCopy())
controllerutil.AddFinalizer(&auto, imagev1.ImageUpdateAutomationFinalizer)
if err := r.Patch(ctx, &auto, patch); err != nil {
log.Error(err, "unable to register finalizer")
return ctrl.Result{}, err
}
}
// If the object is under deletion, record the readiness, and remove our finalizer.
if !auto.ObjectMeta.DeletionTimestamp.IsZero() {
controllerutil.RemoveFinalizer(&auto, imagev1.ImageUpdateAutomationFinalizer)
if err := r.Update(ctx, &auto); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// record suspension metrics
r.RecordSuspend(ctx, &auto, auto.Spec.Suspend)
if auto.Spec.Suspend {
log.Info("ImageUpdateAutomation is suspended, skipping automation run")
return ctrl.Result{}, nil
}
templateValues.AutomationObject = req.NamespacedName
defer func() {
// Always record readiness and duration metrics
r.Metrics.RecordReadiness(ctx, &auto)
r.Metrics.RecordDuration(ctx, &auto, start)
}()
// 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, eventv1.EventSeverityError, err.Error())
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.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 '%s' 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
gitRepoNamespace := req.Namespace
if auto.Spec.SourceRef.Namespace != "" {
gitRepoNamespace = auto.Spec.SourceRef.Namespace
}
originName := types.NamespacedName{
Name: auto.Spec.SourceRef.Name,
Namespace: gitRepoNamespace,
}
debuglog.Info("fetching git repository", "gitrepository", originName)
if r.NoCrossNamespaceRef && gitRepoNamespace != auto.GetNamespace() {
err := acl.AccessDeniedError(fmt.Sprintf("can't access '%s/%s', cross-namespace references have been blocked",
auto.Spec.SourceRef.Kind, originName))
log.Error(err, "access denied to cross-namespaced resource")
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, apiacl.AccessDeniedReason,
err.Error())
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
return ctrl.Result{Requeue: true}, err
}
r.event(ctx, auto, eventv1.EventSeverityError, err.Error())
return ctrl.Result{}, nil
}
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, fmt.Sprintf("referenced git repository %s does not exist.", originName.String()))
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` and git.DefaultBranch will be used.
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 == nil || ref.Branch == "" {
return 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 := os.MkdirTemp("", fmt.Sprintf("%s-%s", originName.Namespace, originName.Name))
if err != nil {
return failWithError(err)
}
defer func() {
if err := os.RemoveAll(tmp); err != nil {
log.Error(err, "failed to remove working directory", "path", tmp)
}
}()
debuglog.Info("attempting to clone git repository", "gitrepository", originName, "ref", ref, "working", tmp)
authOpts, err := r.getAuthOpts(ctx, &origin)
if err != nil {
return failWithError(err)
}
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage()}
forcePush := r.features[features.GitForcePushBranch]
if forcePush && pushBranch != ref.Branch {
clientOpts = append(clientOpts, gogit.WithForcePush())
}
if authOpts.Transport == git.HTTP {
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
}
// 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.
allReferences := r.features[features.GitAllBranchReferences]
if pushBranch != ref.Branch {
clientOpts = append(clientOpts, gogit.WithSingleBranch(!allReferences))
}
gitClient, err := gogit.NewClient(tmp, authOpts, clientOpts...)
if err != nil {
return failWithError(err)
}
defer gitClient.Close()
opts := repository.CloneOptions{}
if ref != nil {
opts.Tag = ref.Tag
opts.SemVer = ref.SemVer
opts.Commit = ref.Commit
opts.Branch = ref.Branch
}
if enabled, _ := r.features[features.GitShallowClone]; enabled {
opts.ShallowClone = true
}
// Use the git operations timeout for the repo.
cloneCtx, cancel := context.WithTimeout(ctx, origin.Spec.Timeout.Duration)
defer cancel()
if _, err := gitClient.Clone(cloneCtx, origin.Spec.URL, opts); 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 && !(ref != nil && ref.Branch == pushBranch) {
// Use the git operations timeout for the repo.
fetchCtx, cancel := context.WithTimeout(ctx, origin.Spec.Timeout.Duration)
defer cancel()
if err := gitClient.SwitchBranch(fetchCtx, pushBranch); err != nil {
return failWithError(err)
}
}
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)
}
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)
p, err := securejoin.SecureJoin(tmp, auto.Spec.Update.Path)
if err != nil {
return failWithError(err)
}
manifestsPath = p
}
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)
}
}
result, err := updateAccordingToSetters(ctx, tracelog, manifestsPath, manifestsPath, policies.Items)
if err != nil {
return failWithError(err)
}
templateValues.Updated = result
default:
log.Info("no update strategy given in the spec")
// no sense rescheduling until this resource changes
r.event(ctx, auto, eventv1.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 {
if signingEntity, err = r.getSigningEntity(ctx, auto); err != nil {
failWithError(err)
}
}
// construct the commit message from template and values
message, err := templateMsg(gitSpec.Commit.MessageTemplate, &templateValues)
if err != nil {
return failWithError(err)
}
var rev string
if len(templateValues.Updated.Files) > 0 {
// 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.
rev, err = gitClient.Commit(
git.Commit{
Author: git.Signature{
Name: gitSpec.Commit.Author.Name,
Email: gitSpec.Commit.Author.Email,
When: time.Now(),
},
Message: message,
},
repository.WithSigner(signingEntity),
)
} else {
err = extgogit.ErrEmptyCommit
}
if err != nil {
if !errors.Is(err, git.ErrNoStagedFiles) && !errors.Is(err, extgogit.ErrEmptyCommit) {
return failWithError(err)
}
log.Info("no changes made in working directory; no commit")
statusMessage = "no updates made"
if auto.Status.LastPushTime != nil && len(auto.Status.LastPushCommit) >= 7 {
statusMessage = fmt.Sprintf("%s; last commit %s at %s", statusMessage, auto.Status.LastPushCommit[:7], auto.Status.LastPushTime.Format(time.RFC3339))
}
} else {
// Use the git operations timeout for the repo.
pushCtx, cancel := context.WithTimeout(ctx, origin.Spec.Timeout.Duration)
defer cancel()
if err := gitClient.Push(pushCtx); err != nil {
return failWithError(err)
}
r.event(ctx, auto, eventv1.EventSeverityInfo, fmt.Sprintf("Committed and pushed change %s to %s\n%s", rev, pushBranch, message))
log.Info("pushed commit to origin", "revision", rev, "branch", pushBranch)
auto.Status.LastPushCommit = rev
auto.Status.LastPushTime = &metav1.Time{Time: start}
statusMessage = "committed and pushed " + rev + " to " + pushBranch
}
// Getting to here is a successful run.
auto.Status.LastAutomationRunTime = &metav1.Time{Time: start}
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionTrue, imagev1.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
}
if r.features == nil {
r.features = features.FeatureGates()
}
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,
RateLimiter: opts.RateLimiter,
RecoverPanic: &opts.RecoverPanic,
}).
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
}
func (r *ImageUpdateAutomationReconciler) getAuthOpts(ctx context.Context, repository *sourcev1.GitRepository) (*git.AuthOptions, error) {
var data map[string][]byte
if repository.Spec.SecretRef != nil {
name := types.NamespacedName{
Namespace: repository.GetNamespace(),
Name: repository.Spec.SecretRef.Name,
}
secret := &corev1.Secret{}
err := r.Client.Get(ctx, name, secret)
if err != nil {
return nil, fmt.Errorf("failed to get secret '%s': %w", name.String(), err)
}
data = secret.Data
}
u, err := url.Parse(repository.Spec.URL)
if err != nil {
return nil, fmt.Errorf("failed to parse URL '%s': %w", repository.Spec.URL, err)
}
opts, err := git.NewAuthOptions(*u, data)
if err != nil {
return nil, fmt.Errorf("failed to configure authentication options: %w", err)
}
return opts, 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)
}
entity := entities[0]
if entity.PrivateKey.Encrypted {
passphrase, ok := secret.Data[signingPassphraseKey]
if !ok {
return nil, fmt.Errorf("can not use passphrase protected signing key without '%s' field present in secret %s",
signingPassphraseKey, 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
}
// --- events, metrics
func (r *ImageUpdateAutomationReconciler) event(ctx context.Context, auto imagev1.ImageUpdateAutomation, severity, msg string) {
eventtype := "Normal"
if severity == eventv1.EventSeverityError {
eventtype = "Warning"
}
r.EventRecorder.Eventf(&auto, eventtype, severity, msg)
}
// --- updates
// updateAccordingToSetters updates files under the root by treating
// the given image policies as kyaml setters.
func updateAccordingToSetters(ctx context.Context, tracelog logr.Logger, inpath, outpath string, policies []imagev1_reflect.ImagePolicy) (update.Result, error) {
return update.UpdateWithSetters(tracelog, inpath, outpath, policies)
}
// 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 {
return "", fmt.Errorf("failed to run template from spec: %w", err)
}
return b.String(), nil
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2025 The Flux authors
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.
@ -14,11 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package constants
package controllers
const (
// SetterShortHand is a shorthand that can be used to mark
// setters; instead of
// # { "$ref": "#/definitions/
SetterShortHand = "$imagepolicy"
import (
"testing"
fuzz "github.com/AdaLogics/go-fuzz-headers"
)
func Fuzz_templateMsg(f *testing.F) {
f.Add("template", []byte{})
f.Add("", []byte{})
f.Fuzz(func(t *testing.T, template string, seed []byte) {
var values TemplateData
fuzz.NewConsumer(seed).GenerateStruct(&values)
_, _ = templateMsg(template, &values)
})
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package controllers
import (
"fmt"
@ -26,16 +26,14 @@ import (
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"
imagev1_reflect "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"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
// +kubebuilder:scaffold:imports
)
@ -46,9 +44,8 @@ import (
// Gomega.
var (
k8sClient client.Client
testEnv *testenv.Environment
ctx = ctrl.SetupSignalHandler()
testEnv *testenv.Environment
ctx = ctrl.SetupSignalHandler()
)
func init() {
@ -69,28 +66,17 @@ func TestMain(m *testing.M) {
}
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))
}
testEnv = testenv.New(testenv.WithCRDPath(
filepath.Join("..", "..", "config", "crd", "bases"),
filepath.Join("testdata", "crds"),
))
controllerName := "image-automation-controller"
if err := (&ImageUpdateAutomationReconciler{
Client: testEnv,
EventRecorder: record.NewFakeRecorder(32),
features: feats,
ControllerName: controllerName,
}).SetupWithManager(ctx, testEnv, ImageUpdateAutomationReconcilerOptions{
Client: testEnv,
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
features: feats,
}).SetupWithManager(testEnv, ImageUpdateAutomationReconcilerOptions{
RateLimiter: controller.GetDefaultRateLimiter(),
}); err != nil {
panic(fmt.Sprintf("failed to start ImageUpdateAutomationReconciler: %v", err))

View File

@ -7,4 +7,4 @@ spec:
spec:
containers:
- name: hello
image: helloworld:1.0.0 # SETTER_SITE
image: helloworld:1.2.0 # SETTER_SITE

View File

@ -0,0 +1 @@
/surely/does/not/exist

File diff suppressed because it is too large Load Diff

View File

@ -19,10 +19,7 @@ limitations under the License.
// states.
package features
import (
"github.com/fluxcd/pkg/auth"
feathelper "github.com/fluxcd/pkg/runtime/features"
)
import feathelper "github.com/fluxcd/pkg/runtime/features"
const (
// GitForcePushBranch enables the use of "force push" when push branches
@ -34,9 +31,6 @@ const (
// 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.
//
@ -58,19 +52,11 @@ var features = map[string]bool{
// 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 {

View File

@ -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"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
"github.com/fluxcd/image-automation-controller/pkg/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 []imagev1_reflect.ImagePolicy) (update.ResultV2, error) {
var result update.ResultV2
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.UpdateV2WithSetters(tracelog, manifestPath, manifestPath, policies)
}

View File

@ -1,173 +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"
imagev1_reflect "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"
"github.com/fluxcd/image-automation-controller/pkg/test"
"github.com/fluxcd/image-automation-controller/pkg/update"
)
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
wantResult update.Result
}{
{
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 := []imagev1_reflect.ImagePolicy{}
for name, image := range tt.policyLatestImages {
policy := &imagev1_reflect.ImagePolicy{}
policy.Name = name
policy.Namespace = testNS
policy.Status = imagev1_reflect.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()
imagev1_reflect.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())
}
test.ExpectMatchingDirectories(g, workDir, expected)
}
})
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,342 +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/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) {
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
}
proxyOpts, proxyURL, err := getProxyOpts(ctx, c, repo)
if err != nil {
return nil, err
}
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 data map[string][]byte
var err error
if repo.Spec.SecretRef != nil {
data, err = getSecretData(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)
}
}
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) {
var opts []auth.Option
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)
}
getCreds = func() (*authutils.GitCredentials, error) {
var opts []github.OptFunc
if len(data) > 0 {
opts = append(opts, github.WithAppData(data))
}
if proxyURL != nil {
opts = append(opts, github.WithProxyURL(proxyURL))
}
if srcOpts.tokenCache != nil {
opts = append(opts, github.WithCache(srcOpts.tokenCache, imagev1.ImageUpdateAutomationKind,
srcOpts.objName, srcOpts.objNamespace, cache.OperationReconcile))
}
username, password, err := github.GetCredentials(ctx, opts...)
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.AppIDKey]; 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 getProxyOpts(ctx context.Context, c client.Client, repo *sourcev1.GitRepository) (*transport.ProxyOptions, *url.URL, error) {
if repo.Spec.ProxySecretRef == nil {
return nil, nil, nil
}
name := repo.Spec.ProxySecretRef.Name
namespace := repo.GetNamespace()
proxyData, err := getSecretData(ctx, c, name, namespace)
if err != nil {
return nil, nil, fmt.Errorf("failed to get proxy secret '%s/%s': %w", namespace, name, err)
}
b, ok := proxyData["address"]
if !ok {
return nil, nil, fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing", namespace, name)
}
address := string(b)
username := string(proxyData["username"])
password := string(proxyData["password"])
proxyOpts := &transport.ProxyOptions{
URL: address,
Username: username,
Password: password,
}
proxyURL, err := url.Parse(string(address))
if err != nil {
return nil, nil, fmt.Errorf("invalid address in proxy secret '%s/%s': %w", namespace, name, err)
}
switch {
case username != "" && password == "":
proxyURL.User = url.User(username)
case username != "" && password != "":
proxyURL.User = url.UserPassword(username, password)
}
return proxyOpts, proxyURL, 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) {
key := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var secret corev1.Secret
if err := c.Get(ctx, key, &secret); err != nil {
return nil, err
}
return secret.Data, nil
}

View File

@ -1,685 +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"
"net/url"
"testing"
"time"
"github.com/go-git/go-git/v5/plumbing/transport"
. "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"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
"github.com/fluxcd/image-automation-controller/internal/testutil"
"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"
)
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.AppIDKey: []byte("123"),
github.AppInstallationIDKey: []byte("456"),
github.AppPrivateKey: []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: "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.AppIDKey: []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_getProxyOpts(t *testing.T) {
namespace := "default"
invalidProxy := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "invalid-proxy",
Namespace: namespace,
},
Data: map[string][]byte{
"url": []byte("https://example.com"),
},
}
validProxy := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "valid-proxy",
Namespace: namespace,
},
Data: map[string][]byte{
"address": []byte("https://example.com"),
"username": []byte("user"),
"password": []byte("pass"),
},
}
tests := []struct {
name string
secretName string
want *transport.ProxyOptions
wantProxyURL *url.URL
wantErr bool
}{
{
name: "non-existing secret",
secretName: "non-existing",
want: nil,
wantProxyURL: nil,
wantErr: true,
},
{
name: "invalid proxy secret",
secretName: "invalid-proxy",
want: nil,
wantProxyURL: nil,
wantErr: true,
},
{
name: "valid proxy secret",
secretName: "valid-proxy",
want: &transport.ProxyOptions{
URL: "https://example.com",
Username: "user",
Password: "pass",
},
wantProxyURL: &url.URL{
Scheme: "https",
Host: "example.com",
User: url.UserPassword("user", "pass"),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
clientBuilder := fakeclient.NewClientBuilder().
WithScheme(scheme.Scheme).
WithObjects(invalidProxy, validProxy)
c := clientBuilder.Build()
gitRepo := &sourcev1.GitRepository{}
gitRepo.Namespace = namespace
if tt.secretName != "" {
gitRepo.Spec = sourcev1.GitRepositorySpec{
ProxySecretRef: &meta.LocalObjectReference{Name: tt.secretName},
}
}
got, gotProxyURL, err := getProxyOpts(context.TODO(), c, gitRepo)
if (err != nil) != tt.wantErr {
g.Fail(fmt.Sprintf("unexpected error: %v", err))
return
}
g.Expect(got).To(Equal(tt.want))
g.Expect(gotProxyURL).To(Equal(tt.wantProxyURL))
})
}
}
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")
}
})
}
}

View File

@ -1,441 +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/pkg/update"
)
// ErrInvalidSourceConfiguration is an error for invalid source configuration.
var ErrInvalidSourceConfiguration = errors.New("invalid source configuration")
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
Updated update.Result
Changed update.ResultV2
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.ResultV2, 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,
Updated: policyResult.ImageResult,
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 {
return "", fmt.Errorf("failed to run template from spec: %w", err)
}
return b.String(), 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

View File

@ -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

View File

@ -1,480 +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"
"github.com/fluxcd/image-automation-controller/internal/constants"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
)
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) *imagev1_reflect.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 &imagev1_reflect.ImageRef{
Name: image,
Tag: tag,
Digest: digest,
}
}

104
main.go
View File

@ -26,17 +26,11 @@ import (
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"
"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"
imagev1_reflect "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"
"github.com/fluxcd/pkg/runtime/client"
helper "github.com/fluxcd/pkg/runtime/controller"
@ -44,22 +38,23 @@ import (
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"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
"github.com/fluxcd/image-automation-controller/internal/features"
"github.com/fluxcd/pkg/git"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
"github.com/fluxcd/image-automation-controller/internal/features"
// +kubebuilder:scaffold:imports
"github.com/fluxcd/image-automation-controller/internal/controller"
"github.com/fluxcd/image-automation-controller/internal/controllers"
)
const (
controllerName = "image-automation-controller"
// recoverPanic indicates whether panic caused by reconciles should be recovered.
recoverPanic = true
)
var (
@ -76,10 +71,6 @@ func init() {
}
func main() {
const (
tokenCacheDefaultMaxSize = 100
)
var (
metricsAddr string
eventsAddr string
@ -92,7 +83,6 @@ func main() {
featureGates feathelper.FeatureGates
watchOptions helper.WatchOptions
concurrent int
tokenCacheOptions cache.TokenFlags
)
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
@ -111,7 +101,6 @@ func main() {
rateLimiterOptions.BindFlags(flag.CommandLine)
featureGates.BindFlags(flag.CommandLine)
watchOptions.BindFlags(flag.CommandLine)
tokenCacheOptions.BindFlags(flag.CommandLine, tokenCacheDefaultMaxSize)
flag.Parse()
@ -124,14 +113,6 @@ func main() {
os.Exit(1)
}
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()
}
watchNamespace := ""
if !watchOptions.AllNamespaces {
watchNamespace = os.Getenv("RUNTIME_NAMESPACE")
@ -155,53 +136,39 @@ func main() {
os.Exit(1)
}
selectingCacheFunc := cache.BuilderWithOptions(cache.Options{
SelectorsByObject: cache.SelectorsByObject{
&imagev1.ImageUpdateAutomation{}: {Label: watchSelector},
},
})
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,
NewCache: selectingCacheFunc,
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)
Namespace: watchNamespace,
ClientDisableCacheFor: disableCacheFor,
})
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 {
@ -209,32 +176,17 @@ func main() {
os.Exit(1)
}
metricsH := helper.NewMetrics(mgr, metrics.MustMakeRecorder(), imagev1.ImageUpdateAutomationFinalizer)
metricsH := helper.MustMakeMetrics(mgr)
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{
if err := (&controllers.ImageUpdateAutomationReconciler{
Client: mgr.GetClient(),
EventRecorder: eventRecorder,
Metrics: metricsH,
NoCrossNamespaceRef: aclOptions.NoCrossNamespaceRefs,
ControllerName: controllerName,
}).SetupWithManager(ctx, mgr, controller.ImageUpdateAutomationReconcilerOptions{
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
TokenCache: tokenCache,
}).SetupWithManager(mgr, controllers.ImageUpdateAutomationReconcilerOptions{
MaxConcurrentReconciles: concurrent,
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
RecoverPanic: recoverPanic,
}); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ImageUpdateAutomation")
os.Exit(1)
@ -242,7 +194,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)
}

View File

@ -93,7 +93,7 @@ func (r *ScreeningLocalReader) Read() ([]*yaml.RNode, error) {
return nil
}
if ext := filepath.Ext(p); ext != ".yaml" && ext != ".yml" && filepath.Base(p) != "Kustomization" {
if ext := filepath.Ext(p); ext != ".yaml" && ext != ".yml" {
return nil
}

View File

@ -33,12 +33,11 @@ func TestScreeningLocalReader(t *testing.T) {
}
nodes, err := r.Read()
g.Expect(err).ToNot(HaveOccurred())
// the test fixture has four files that contain the marker:
// - marked.yaml
// the test fixture has three files that contain the marker:
// - otherns.yaml
// - kustomization.yml
// - Kustomization
g.Expect(len(nodes)).To(Equal(4))
// - marked.yaml
// - kustomization.yaml
g.Expect(len(nodes)).To(Equal(3))
filesSeen := map[string]struct{}{}
for i := range nodes {
path, _, err := kioutil.GetFileAnnotations(nodes[i])
@ -46,10 +45,9 @@ func TestScreeningLocalReader(t *testing.T) {
filesSeen[path] = struct{}{}
}
g.Expect(filesSeen).To(Equal(map[string]struct{}{
"marked.yaml": {},
"otherns.yaml": {},
"kustomization.yml": {},
"Kustomization": {},
"marked.yaml": {},
"kustomization.yaml": {},
"otherns.yaml": {},
}))
}

View File

@ -17,12 +17,11 @@ limitations under the License.
package update
import (
"encoding/json"
"github.com/go-logr/logr"
"k8s.io/kube-openapi/pkg/validation/spec"
"sigs.k8s.io/kustomize/kyaml/fieldmeta"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/setters2"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@ -113,17 +112,8 @@ func accept(v visitor, object *yaml.RNode, p string, settersSchema *spec.Schema)
return nil
}
type setter struct {
Name string `json:"name"`
Value string `json:"value"`
}
type extension struct {
Setter *setter `json:"setter,omitempty"`
}
// set applies the value from ext to field
func (s *SetAllCallback) set(field *yaml.RNode, ext *extension, sch *spec.Schema) (bool, error) {
func (s *SetAllCallback) set(field *yaml.RNode, ext *setters2.CliExtension, sch *spec.Schema) (bool, error) {
// check full setter
if ext.Setter == nil {
return false, nil
@ -149,7 +139,7 @@ func (s *SetAllCallback) visitScalar(object *yaml.RNode, p string, fieldSchema *
return nil
}
// get the openAPI for this field describing how to apply the setter
ext, err := getExtFromSchema(fieldSchema.Schema)
ext, err := setters2.GetExtFromSchema(fieldSchema.Schema)
if err != nil {
return err
}
@ -162,19 +152,3 @@ func (s *SetAllCallback) visitScalar(object *yaml.RNode, p string, fieldSchema *
_, err = s.set(object, ext, fieldSchema.Schema)
return err
}
func getExtFromSchema(schema *spec.Schema) (*extension, error) {
cep := schema.VendorExtensible.Extensions[K8sCliExtensionKey]
if cep == nil {
return nil, nil
}
b, err := json.Marshal(cep)
if err != nil {
return nil, err
}
val := &extension{}
if err := json.Unmarshal(b, val); err != nil {
return nil, err
}
return val, nil
}

View File

@ -1,106 +0,0 @@
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))
}
})
}
}

View File

@ -96,66 +96,3 @@ func (r Result) Objects() map[ObjectIdentifier][]ImageRef {
}
return result
}
// ResultV2 contains Result of update and also the file changes made during the
// update. This extends the Result to include details about the exact changes
// made to the files and the objects in them. It has a nested structure
// file->objects->changes.
type ResultV2 struct {
ImageResult Result
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 Resultv2 for a given file, object and changes
// associated with it.
func (r *ResultV2) 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 ResultV2) 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 ResultV2) Objects() ObjectChanges {
result := make(ObjectChanges)
for _, objChanges := range r.FileChanges {
for obj, change := range objChanges {
result[obj] = change
}
}
return result
}

View File

@ -92,80 +92,3 @@ func TestUpdateResults(t *testing.T) {
},
}))
}
func TestResultV2(t *testing.T) {
g := NewWithT(t)
var result ResultV2
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 = ResultV2{
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",
},
},
}))
}

View File

@ -18,6 +18,7 @@ package update
import (
"fmt"
"strings"
"github.com/go-logr/logr"
"github.com/google/go-containerregistry/pkg/name"
@ -28,19 +29,21 @@ import (
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/sets"
"sigs.k8s.io/kustomize/kyaml/setters2"
"sigs.k8s.io/kustomize/kyaml/yaml"
"github.com/fluxcd/image-automation-controller/internal/constants"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
)
const (
// This is preserved from setters2
K8sCliExtensionKey = "x-k8s-cli"
// SetterShortHand is a shorthand that can be used to mark
// setters; instead of
// # { "$ref": "#/definitions/
SetterShortHand = "$imagepolicy"
)
func init() {
fieldmeta.SetShortHandRef(constants.SetterShortHand)
fieldmeta.SetShortHandRef(SetterShortHand)
// this prevents the global schema, should it be initialised, from
// parsing all the Kubernetes openAPI definitions, which is not
// necessary.
@ -51,15 +54,6 @@ func init() {
// that contain an "in scope" image policy marker, and writes files it
// updated (and only those files) back to `outpath`.
func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []imagev1_reflect.ImagePolicy) (Result, error) {
result, err := UpdateV2WithSetters(tracelog, inpath, outpath, policies)
return result.ImageResult, err
}
// UpdateV2WithSetters takes all YAML files from `inpath`, updates any
// that contain an "in scope" image policy marker, and writes files it
// updated (and only those files) back to `outpath`. It also returns the result
// of the changes it made as ResultV2.
func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies []imagev1_reflect.ImagePolicy) (ResultV2, error) {
// the OpenAPI schema is a package variable in kyaml/openapi. In
// lieu of being able to isolate invocations (per
// https://github.com/kubernetes-sigs/kustomize/issues/3058), I
@ -103,15 +97,13 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies
Files: make(map[string]FileResult),
}
var resultV2 ResultV2
// Compilng the result needs the file, the image ref used, and the
// object. Each setter will supply its own name to its callback,
// which can be used to look up the image ref; the file and object
// we will get from `setAll` which keeps track of those as it
// iterates.
imageRefs := make(map[string]imageRef)
setAllCallback := func(file, setterName string, node *yaml.RNode, old, new string) {
setAllCallback := func(file, setterName string, node *yaml.RNode) {
ref, ok := imageRefs[setterName]
if !ok {
return
@ -123,15 +115,6 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies
}
oid := ObjectIdentifier{meta.GetIdentifier()}
// Record the change.
ch := Change{
OldValue: old,
NewValue: new,
Setter: setterName,
}
// Append the change for the file and identifier.
resultV2.AddChange(file, oid, ch)
fileres, ok := result.Files[file]
if !ok {
fileres = FileResult{
@ -139,7 +122,7 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies
}
result.Files[file] = fileres
}
objres := fileres.Objects[oid]
objres, ok := fileres.Objects[oid]
for _, n := range objres {
if n == ref {
return
@ -151,7 +134,7 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies
defs := map[string]spec.Schema{}
for _, policy := range policies {
if policy.Status.LatestRef == nil {
if policy.Status.LatestImage == "" {
continue
}
// Using strict validation would mean any image that omits the
@ -160,10 +143,10 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies
// filled in. Usually this would mean the tag would end up
// being `latest` if empty in the input; but I'm assuming here
// that the policy won't have a tagless ref.
image := policy.Status.LatestRef.String()
image := policy.Status.LatestImage
r, err := name.ParseReference(image, name.WeakValidation)
if err != nil {
return ResultV2{}, fmt.Errorf("encountered invalid image ref %q: %w", image, err)
return Result{}, fmt.Errorf("encountered invalid image ref %q: %w", policy.Status.LatestImage, err)
}
ref := imageRef{
Reference: r,
@ -173,13 +156,15 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies
},
}
tag := policy.Status.LatestRef.Tag
name := policy.Status.LatestRef.Name
digest := policy.Status.LatestRef.Digest
tag := ref.Identifier()
// annoyingly, neither the library imported above, nor an
// alternative I found, will yield the original image name;
// this is an easy way to get it
name := strings.TrimSuffix(image, ":"+tag)
imageSetter := fmt.Sprintf("%s:%s", policy.GetNamespace(), policy.GetName())
tracelog.Info("adding setter", "name", imageSetter)
defs[fieldmeta.SetterDefinitionPrefix+imageSetter] = setterSchema(imageSetter, image)
defs[fieldmeta.SetterDefinitionPrefix+imageSetter] = setterSchema(imageSetter, policy.Status.LatestImage)
imageRefs[imageSetter] = ref
tagSetter := imageSetter + ":tag"
@ -187,15 +172,11 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies
defs[fieldmeta.SetterDefinitionPrefix+tagSetter] = setterSchema(tagSetter, tag)
imageRefs[tagSetter] = ref
// Context().Name() gives the image repository _as supplied_
nameSetter := imageSetter + ":name"
tracelog.Info("adding setter", "name", nameSetter)
defs[fieldmeta.SetterDefinitionPrefix+nameSetter] = setterSchema(nameSetter, name)
imageRefs[nameSetter] = ref
digestSetter := imageSetter + ":digest"
tracelog.Info("adding setter", "name", digestSetter)
defs[fieldmeta.SetterDefinitionPrefix+digestSetter] = setterSchema(digestSetter, digest)
imageRefs[digestSetter] = ref
}
settersSchema.Definitions = defs
@ -203,7 +184,7 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies
// get ready with the reader and writer
reader := &ScreeningLocalReader{
Path: inpath,
Token: fmt.Sprintf("%q", constants.SetterShortHand),
Token: fmt.Sprintf("%q", SetterShortHand),
Trace: tracelog,
}
writer := &kio.LocalPackageWriter{
@ -221,12 +202,9 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies
// go!
err := pipeline.Execute()
if err != nil {
return ResultV2{}, err
return Result{}, err
}
// Combine the results.
resultV2.ImageResult = result
return resultV2, nil
return result, nil
}
// setAll returns a kio.Filter using the supplied SetAllCallback
@ -235,7 +213,7 @@ func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies
// files with changed nodes. This is based on
// [`SetAll`](https://github.com/kubernetes-sigs/kustomize/blob/kyaml/v0.10.16/kyaml/setters2/set.go#L503
// from kyaml/kio.
func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, setterName string, node *yaml.RNode, old, new string)) kio.Filter {
func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, setterName string, node *yaml.RNode)) kio.Filter {
filter := &SetAllCallback{
SettersSchema: schema,
Trace: tracelog,
@ -251,7 +229,7 @@ func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, sette
filter.Callback = func(setter, oldValue, newValue string) {
if newValue != oldValue {
callback(path, setter, nodes[i], oldValue, newValue)
callback(path, setter, nodes[i])
filesToUpdate.Insert(path)
}
}
@ -278,7 +256,7 @@ func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, sette
func setterSchema(name, value string) spec.Schema {
schema := spec.StringProperty()
schema.Extensions = map[string]interface{}{}
schema.Extensions.Add(K8sCliExtensionKey, map[string]interface{}{
schema.Extensions.Add(setters2.K8sCliExtensionKey, map[string]interface{}{
"setter": map[string]string{
"name": name,
"value": value,

View File

@ -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: index.repo.fake/updated # {"$imagepolicy": "automation-ns:policy:name"}
newTag: v1.0.1 # {"$imagepolicy": "automation-ns:policy:tag"}

View File

@ -0,0 +1,9 @@
# 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"}

View File

@ -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"}

View File

@ -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"}

View File

@ -0,0 +1,9 @@
# 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"}

View File

@ -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"}

View File

@ -17,6 +17,8 @@ limitations under the License.
package update
import (
"io/ioutil"
"os"
"testing"
"github.com/go-logr/logr"
@ -26,7 +28,6 @@ import (
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/kustomize/kyaml/yaml"
"github.com/fluxcd/image-automation-controller/internal/testutil"
"github.com/fluxcd/image-automation-controller/pkg/test"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
)
@ -36,35 +37,29 @@ func TestUpdateWithSetters(t *testing.T) {
policies := []imagev1_reflect.ImagePolicy{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: metav1.ObjectMeta{ // name matches marker used in testdata/setters/{original,expected}
Namespace: "automation-ns",
Name: "policy",
},
Status: imagev1_reflect.ImagePolicyStatus{
LatestRef: testutil.ImageToRef("index.repo.fake/updated:v1.0.1"),
LatestImage: "index.repo.fake/updated:v1.0.1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: metav1.ObjectMeta{ // name matches marker used in testdata/setters/{original,expected}
Namespace: "automation-ns",
Name: "unchanged",
},
Status: imagev1_reflect.ImagePolicyStatus{
LatestRef: testutil.ImageToRef("image:v1.0.0"),
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "automation-ns",
Name: "policy-with-digest",
},
Status: imagev1_reflect.ImagePolicyStatus{
LatestRef: testutil.ImageToRef("image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be"),
LatestImage: "image:v1.0.0",
},
},
}
tmp := t.TempDir()
tmp, err := ioutil.TempDir("", "gotest")
g.Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmp)
result, err := UpdateWithSetters(logr.Discard(), "testdata/setters/original", tmp, policies)
g.Expect(err).ToNot(HaveOccurred())
test.ExpectMatchingDirectories(g, tmp, "testdata/setters/expected")
@ -85,30 +80,15 @@ func TestUpdateWithSetters(t *testing.T) {
Name: "foo",
},
}}
r, _ := name.ParseReference("index.repo.fake/updated:v1.0.1")
expectedImageRef := imageRef{r, types.NamespacedName{
Name: "policy",
Namespace: "automation-ns",
}}
r, _ = name.ParseReference("image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be")
expectedImageRefDigest := imageRef{r, types.NamespacedName{
Name: "policy-with-digest",
Namespace: "automation-ns",
}}
expectedResult := Result{
Files: map[string]FileResult{
"kustomization.yml": {
Objects: map[ObjectIdentifier][]ImageRef{
kustomizeResourceID: {
expectedImageRef,
expectedImageRefDigest,
},
},
},
"Kustomization": {
"kustomization.yaml": {
Objects: map[ObjectIdentifier][]ImageRef{
kustomizeResourceID: {
expectedImageRef,
@ -126,65 +106,4 @@ func TestUpdateWithSetters(t *testing.T) {
}
g.Expect(result).To(Equal(expectedResult))
// Test ResultV2.
tmp2 := t.TempDir()
resultV2, err := UpdateV2WithSetters(logr.Discard(), "testdata/setters/original", tmp2, policies)
g.Expect(err).ToNot(HaveOccurred())
test.ExpectMatchingDirectories(g, tmp2, "testdata/setters/expected")
expectedResultV2 := ResultV2{
ImageResult: expectedResult,
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(resultV2).To(Equal(expectedResultV2))
}

View File

@ -1,11 +1,5 @@
FROM gcr.io/oss-fuzz-base/base-builder-go
RUN wget https://go.dev/dl/go1.24.0.linux-amd64.tar.gz \
&& mkdir temp-go \
&& rm -rf /root/.go/* \
&& tar -C temp-go/ -xzf go1.24.0.linux-amd64.tar.gz \
&& mv temp-go/go/* /root/.go/
ENV SRC=$GOPATH/src/github.com/fluxcd/image-automation-controller
ENV ROOT_ORG=$SRC
ENV FLUX_CI=true

View File

@ -21,8 +21,8 @@ set -euxo pipefail
# Some tests requires embedded resources. Embedding does not allow
# for traversing into ascending dirs, therefore we copy those contents here:
mkdir -p internal/controller/testdata/crd
cp config/crd/bases/*.yaml internal/controller/testdata/crd
mkdir -p internal/controllers/testdata/crd
cp config/crd/bases/*.yaml internal/controllers/testdata/crd
# Version of the source-controller from which to get the GitRepository CRD.
# Pulls source-controller/api's version set in go.mod.
@ -32,10 +32,10 @@ SOURCE_VER=$(go list -m github.com/fluxcd/source-controller/api | awk '{print $2
# Pulls image-reflector-controller/api's version set in go.mod.
REFLECTOR_VER=$(go list -m github.com/fluxcd/image-reflector-controller/api | awk '{print $2}')
if [ -d "../../internal/controller/testdata/crds" ]; then
cp ../../internal/controller/testdata/crds/*.yaml testdata/crds
if [ -d "../../internal/controllers/testdata/crds" ]; then
cp ../../internal/controllers/testdata/crds/*.yaml testdata/crds
else
# Fetch the CRDs if not present since we need them when running fuzz tests on CI.
curl -s --fail https://raw.githubusercontent.com/fluxcd/source-controller/${SOURCE_VER}/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml -o internal/controller/testdata/crd/gitrepositories.yaml
curl -s --fail https://raw.githubusercontent.com/fluxcd/image-reflector-controller/${REFLECTOR_VER}/config/crd/bases/image.toolkit.fluxcd.io_imagepolicies.yaml -o internal/controller/testdata/crd/imagepolicies.yaml
curl -s --fail https://raw.githubusercontent.com/fluxcd/source-controller/${SOURCE_VER}/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml -o internal/controllers/testdata/crd/gitrepositories.yaml
curl -s --fail https://raw.githubusercontent.com/fluxcd/image-reflector-controller/${REFLECTOR_VER}/config/crd/bases/image.toolkit.fluxcd.io_imagepolicies.yaml -o internal/controllers/testdata/crd/imagepolicies.yaml
fi