Compare commits

..

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

99 changed files with 5839 additions and 12449 deletions

View File

@ -1 +1 @@
build/
build/libgit2/

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.19.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.19.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.19.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.19.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,18 @@ 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
with:
platforms: all
- 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@v3
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
@ -39,27 +32,27 @@ jobs:
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF/refs\/tags\//}
fi
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
echo ::set-output name=BUILD_DATE::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
echo ::set-output name=VERSION::${VERSION}
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@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,11 +60,8 @@ 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@v3
with:
sbom: true
provenance: true
push: true
builder: ${{ steps.buildx.outputs.name }}
context: .
@ -79,82 +69,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@main
- 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 fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
cosign sign 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@v3
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@v1
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.19.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

7
.gitignore vendored
View File

@ -2,7 +2,7 @@ notes
# Thes are downloaded in the Makefile
cache/*
internal/controller/testdata/crds/*
controllers/testdata/crds/*
# Binaries for programs and plugins
*.exe
@ -29,8 +29,5 @@ testbin
*.swo
*~
# Exclude all build related files
# Exclude all libgit2 related files
build/
# CRDs for fuzzing tests.
internal/controller/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:
@ -49,7 +32,6 @@ signs:
certificate: "${artifact}.pem"
args:
- sign-blob
- "--yes"
- "--output-certificate=${certificate}"
- "--output-signature=${signature}"
- "${artifact}"

1291
ATTRIBUTIONS.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,750 +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
This prerelease comes with support for signing commits using password protected OpenPGP keys.
In addition, the controller dependencies have been updated to their latest
versions.
Improvements:
- Add support for commit signing PGP key passphrases
[#510](https://github.com/fluxcd/image-automation-controller/pull/510)
- Update dependencies
[#511](https://github.com/fluxcd/image-automation-controller/pull/511)
## 0.32.0
**Release date:** 2023-03-31
This prerelease updates the dependencies to their latest versions.
In addition, the controller now supports horizontal scaling using sharding based on a label selector.
### Highlights
#### API Changes
This prerelease is only compatible with `GitRepository` API version `v1`, first shipped with
[source-controller](https://github.com/fluxcd/source-controller) v1.0.0-rc.1.
#### Sharding
Starting with this release, the controller can be configured with `--watch-label-selector`, after
which only objects with this label will be reconciled by the controller.
This allows for horizontal scaling, where image-automation-controller can be deployed multiple times
with a unique label selector which is used as the sharding key.
### Full Changelog
Improvements:
- move `controllers` to `internal/controllers`
[#500](https://github.com/fluxcd/image-automation-controller/pull/500)
- Add reconciler sharding capability based on label selector
[#504](https://github.com/fluxcd/image-automation-controller/pull/504)
- Update dependencies and GitRepository API to v1
[#505](https://github.com/fluxcd/image-automation-controller/pull/505)
- bump google.golang.org/protobuf from 1.29.0 to 1.29.1
[#506](https://github.com/fluxcd/image-automation-controller/pull/506)
## 0.31.0
**Release date:** 2023-03-08
This release updates to Go version the controller is build with to 1.20, and
updates the dependencies to their latest versions.
In addition, `klog` is now configured to log using the same logger as the rest
of the controller (providing a consistent log format).
Improvements:
- Update Go to 1.20
[#492](https://github.com/fluxcd/image-automation-controller/pull/492)
- Update dependencies
[#494](https://github.com/fluxcd/image-automation-controller/pull/494)
[#496](https://github.com/fluxcd/image-automation-controller/pull/496)
- Use `logger.SetLogger` to also configure `klog`
[#495](https://github.com/fluxcd/image-automation-controller/pull/495)
## 0.30.0
**Release date:** 2023-02-16
This prerelease comes with support for the new `ImagePolicy` v1beta2 API. See
[image-reflector-controller
changelog](https://github.com/fluxcd/image-reflector-controller/blob/v0.25.0/CHANGELOG.md#0250)
for instructions about updating the `ImagePolicies`.
:warning: Also note that the autologin flags in image-reflector-controller have
been deprecated and the `ImageRepositories` that use the autologin feature have
to be updated with `.spec.provider` field to continue working. Refer the
[docs](https://fluxcd.io/flux/components/image/imagerepositories/#provider) for
details and examples.
In addition, the controller dependencies have been updated to their latest
versions.
Improvements:
- Set rate limiter option in test reconcilers
[#475](https://github.com/fluxcd/image-automation-controller/pull/475)
- Update dependencies
[#486](https://github.com/fluxcd/image-automation-controller/pull/486)
- Update image-reflector API to v1beta2
[#485](https://github.com/fluxcd/image-automation-controller/pull/485)
## 0.29.0
**Release date:** 2023-02-01
This prerelease disables caching of Secrets and ConfigMaps to improve memory
usage. To opt-out from this behavior, start the controller with:
`--feature-gates=CacheSecretsAndConfigMaps=true`.
In addition, the controller dependencies have been updated to Kubernetes
v1.26.1 and controller-runtime v0.14.2. The controller base image has been
updated to Alpine 3.17.
Improvements:
- build: Enable SBOM and SLSA Provenance
[#478](https://github.com/fluxcd/image-automation-controller/pull/478)
- Disable caching of Secrets and ConfigMaps
[#479](https://github.com/fluxcd/image-automation-controller/pull/479)
- Update dependencies
[#480](https://github.com/fluxcd/image-automation-controller/pull/480)
## 0.28.0
**Release date:** 2022-12-21
This prerelease removes all code references to `libgit2` and `git2go`, from
this release onwards the controller will use `go-git` as the only git implementation.
For more information, refer to version 0.27.0's changelog, which started `libgit2`'s
deprecation process.
The feature gate `ForceGoGitImplementation` was removed, users passing it as their
controller's startup args will need to remove it before upgrading.
Two new feature gates were introduced and are enabled by default:
- `GitShallowClone`: enables the use of shallow clones when pulling source
from Git repositories.
- `GitAllBranchReferences`: enables users to toggle the download of all branch
head references when push branches are configured.
To opt-out from the feature gates above, start the controller with:
`--feature-gates=GitShallowClone=false,GitAllBranchReferences=false`.
Fixes:
- Block the creation of empty commits
[#470](https://github.com/fluxcd/image-automation-controller/pull/470)
Improvements:
- Add GitShallowClone feature
[#463](https://github.com/fluxcd/image-automation-controller/pull/463)
- Add feature gate GitAllBranchReferences
[#469](https://github.com/fluxcd/image-automation-controller/pull/469)
- Remove libgit2 and git2go from codebase
[#468](https://github.com/fluxcd/image-automation-controller/pull/468)
- build: Link libgit2 via LIB_FUZZING_ENGINE
[#465](https://github.com/fluxcd/image-automation-controller/pull/465)
- build: Add postbuild script for fuzzing
[#464](https://github.com/fluxcd/image-automation-controller/pull/464)
- build: Fix cifuzz and improve fuzz tests' reliability
[#462](https://github.com/fluxcd/image-automation-controller/pull/462)
- Update dependencies
[#471](https://github.com/fluxcd/image-automation-controller/pull/471)
## 0.27.0
**Release date:** 2022-11-21
This prerelease comes with a major refactoring of the controller's Git operations.
The controller can now observe the field `spec.gitImplementation` instead of always
ignoring it and using `libgit2`. The `go-git` implementation now supports all Git
servers, including Azure DevOps, which previously was only supported by `libgit2`.
By default, the field `spec.gitImplementation` is ignored and the reconciliations
will use `go-git`. To opt-out from this behaviour, and get the controller to
honour the field `spec.gitImplementation`, start the controller with:
`--feature-gates=ForceGoGitImplementation=false`.
This version initiates the soft deprecation of the `libgit2` implementation.
The motivation for removing support for `libgit2` being:
- Reliability: over the past months we managed to substantially reduce the
issues users experienced, but there are still crashes happening when the controller
runs over longer periods of time, or when under intense GC pressure.
- Performance: due to the inherit nature of `libgit2` implementation, which
is a C library called via CGO through `git2go`, it will never perform as well as
a pure Go implementations. At scale, memory pressure insues which then triggers
the reliability issues above.
- Lack of Shallow Clone Support.
- Maintainability: supporting two Git implementations is a big task, even more
so when one of them is in a complete different tech stack. Given its nature, to
support `libgit2`, we have to maintain an additional repository. Statically built
`libgit2` libraries need to be cross-compiled for all our supported platforms.
And a lot of "unnecessary" code has to be in place to make building, testing and
fuzzing work seamlessly.
Users having any issues with `go-git` should report it to the Flux team,
so any issues can be resolved before support for `libgit2` is completely
removed from the codebase.
Starting from this version `ImageUpdateAutomation` objects with a `spec.PushBranch`
specified will have the push branch refreshed automatically via force push.
To opt-out from this behaviour, start the controller with:
`--feature-gates=GitForcePushBranch=false`.
Improvements:
- Refactor Git operations and introduce go-git support for Azure DevOps and AWS CodeCommit
[#451](https://github.com/fluxcd/image-automation-controller/pull/451)
- Add new ForceGoGitImplementation FeatureGate
[#452](https://github.com/fluxcd/image-automation-controller/pull/452)
- Add support for force push
[#453](https://github.com/fluxcd/image-automation-controller/pull/453)
- Use Flux Event API v1beta1
[#455](https://github.com/fluxcd/image-automation-controller/pull/455)
- Remove deprecated alpha APIs
[#456](https://github.com/fluxcd/image-automation-controller/pull/456)
- Remove nsswitch.conf creation
[#458](https://github.com/fluxcd/image-automation-controller/pull/458)
- Update Dependencies
[#459](https://github.com/fluxcd/image-automation-controller/pull/459)
[#460](https://github.com/fluxcd/image-automation-controller/pull/460)
## 0.26.1
**Release date:** 2022-10-21

View File

@ -13,7 +13,80 @@ There are a number of dependencies required to be able to run the controller and
- [Install Docker](https://docs.docker.com/engine/install/)
- (Optional) [Install Kubebuilder](https://book.kubebuilder.io/quick-start.html#installation)
The following dependencies are also used by some of the `make` targets:
The dependency [libgit2](https://libgit2.org/) also needs to be installed to be able
to run `image-automation-controller` or its test-suite locally (not in a container).
In case this dependency is not present on your system (at the expected
version), the first invocation of a `make` target that requires the
dependency will attempt to compile it locally to `hack/libgit2`. For this build
to succeed ensure the following dependencies are present on your system:
- [CMake](https://cmake.org/download/)
- [OpenSSL 1.1](https://www.openssl.org/source/)
- [LibSSH2](https://www.libssh2.org/)
- [pkg-config](https://freedesktop.org/wiki/Software/pkg-config/)
Triggering a manual build of the dependency is possible as well by running
`make libgit2`. To enforce the build, for example if your system dependencies
match but are not linked in a compatible way, append `LIBGIT2_FORCE=1` to the
`make` command.
Follow the instructions below to install these dependencies to your system.
### macOS
```console
$ # Ensure libgit2 dependencies are available
$ brew install cmake openssl@1.1 libssh2 pkg-config
$ LIBGIT2_FORCE=1 make libgit2
```
#### openssl and pkg-config
You may see this message when trying to run a build:
```
# pkg-config --cflags libgit2
Package openssl was not found in the pkg-config search path.
Perhaps you should add the directory containing `openssl.pc'
to the PKG_CONFIG_PATH environment variable
Package 'openssl', required by 'libgit2', not found
pkg-config: exit status 1
```
On some macOS systems `brew` will not link the pkg-config file for
openssl into expected directory, because it clashes with a
system-provided openssl. When installing it, or if you do
```console
brew link openssl@1.1
Warning: Refusing to link macOS provided/shadowed software: openssl@1.1
If you need to have openssl@1.1 first in your PATH, run:
echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.profile
For compilers to find openssl@1.1 you may need to set:
export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
For pkg-config to find openssl@1.1 you may need to set:
export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"
```
.. it tells you (in the last part of the message) how to add `openssl`
to your `PKG_CONFIG_PATH` so it can be found.
### Linux
```console
$ # Ensure libgit2 dependencies are available
$ pacman -S cmake openssl libssh2
$ LIBGIT2_FORCE=1 make libgit2
```
**Note:** Example shown is for Arch Linux, but likewise procedure can be
followed using any other package manager, e.g. `apt`.
In addition to the above, the following dependencies are also used by some of the `make` targets:
- `controller-gen` (v0.7.0)
- `gen-crd-api-reference-docs` (v0.3.0)
@ -24,7 +97,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
@ -96,11 +169,18 @@ Create a `.vscode/launch.json` file:
"type": "go",
"request": "launch",
"mode": "auto",
"envFile": "${workspaceFolder}/build/.env",
"program": "${workspaceFolder}/main.go"
}
]
}
```
Create the environment file containing details on how to load
`libgit2` dependencies:
```bash
make env
```
Start debugging by either clicking `Run` > `Start Debugging` or using
the relevant shortcut.

View File

@ -1,10 +1,15 @@
ARG BASE_VARIANT=alpine
ARG GO_VERSION=1.24
ARG XX_VERSION=1.6.1
ARG GO_VERSION=1.19
ARG XX_VERSION=1.1.2
ARG LIBGIT2_IMG=ghcr.io/fluxcd/golang-with-libgit2-only
ARG LIBGIT2_TAG=v0.4.0
FROM ${LIBGIT2_IMG}:${LIBGIT2_TAG} AS libgit2-libs
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
@ -12,18 +17,12 @@ FROM gostable AS go-linux
# These will be used at current arch to yield execute the cross compilations.
FROM go-${TARGETOS} AS build-base
RUN apk add clang lld
RUN apk add clang lld pkgconfig
COPY --from=xx / /
# build can still be cached at build platform architecture.
FROM build-base AS build
ARG TARGETPLATFORM
# Some dependencies have to installed for the target platform:
# https://github.com/tonistiigi/xx#go--cgo
RUN xx-apk add musl-dev gcc
# build-go-mod can still be cached at build platform architecture.
FROM build-base as build-go-mod
# Configure workspace
WORKDIR /workspace
@ -38,20 +37,33 @@ COPY go.sum go.sum
# Cache modules
RUN go mod download
# Build stage install per target platform
# dependency and effectively cross compile the application.
FROM build-go-mod as build
ARG TARGETPLATFORM
COPY --from=libgit2-libs /usr/local/ /usr/local/
# Some dependencies have to installed
# for the target platform: https://github.com/tonistiigi/xx#go--cgo
RUN xx-apk add musl-dev gcc clang lld
WORKDIR /workspace
# Copy source code
COPY main.go main.go
COPY controllers/ controllers/
COPY pkg/ pkg/
COPY internal/ internal/
ARG TARGETPLATFORM
ARG TARGETARCH
# Reasons why CGO is in use:
# - The SHA1 implementation (sha1cd) used by go-git depends on CGO for
# performance reasons. See: https://github.com/pjbgf/sha1cd/issues/15
ENV CGO_ENABLED=1
RUN export CGO_LDFLAGS="-static -fuse-ld=lld" && \
RUN export LIBRARY_PATH="/usr/local/$(xx-info triple)" && \
export PKG_CONFIG_PATH="/usr/local/$(xx-info triple)/lib/pkgconfig" && \
export CGO_LDFLAGS="$(pkg-config --static --libs --cflags libgit2) -static -fuse-ld=lld" && \
xx-go build \
-ldflags "-s -w" \
-tags 'netgo,osusergo,static_build' \
@ -60,7 +72,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.16
ARG TARGETPLATFORM
RUN apk --no-cache add ca-certificates \
@ -68,6 +80,7 @@ RUN apk --no-cache add ca-certificates \
# Copy over binary from build
COPY --from=build /image-automation-controller /usr/local/bin/
COPY ATTRIBUTIONS.md /
USER 65534:65534
ENTRYPOINT [ "image-automation-controller" ]

View File

@ -9,8 +9,7 @@ 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)
Paulo Gomes, Weaveworks <paulo.gomes@weave.works> (github: @pjbgf, slack: pjbgf)
Retired maintainers:

View File

@ -6,6 +6,10 @@ TAG ?= latest
# Produce CRDs that work back to Kubernetes 1.16
CRD_OPTIONS ?= crd:crdVersions=v1
# Base image used to build the Go binary
LIBGIT2_IMG ?= ghcr.io/fluxcd/golang-with-libgit2-only
LIBGIT2_TAG ?= v0.4.0
# Allows for defining additional Docker buildx arguments,
# e.g. '--push'.
BUILD_ARGS ?=
@ -33,10 +37,26 @@ REFLECTOR_VER ?= $(shell go list -m github.com/fluxcd/image-reflector-controller
REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel)
BUILD_DIR := $(REPOSITORY_ROOT)/build
# Other dependency versions
ENVTEST_BIN_VERSION ?= 1.19.2
# FUZZ_TIME defines the max amount of time, in Go Duration,
# each fuzzer should run for.
FUZZ_TIME ?= 1m
# Caches libgit2 versions per tag, "forcing" rebuild only when needed.
LIBGIT2_PATH := $(BUILD_DIR)/libgit2/$(LIBGIT2_TAG)
LIBGIT2_LIB_PATH := $(LIBGIT2_PATH)/lib
LIBGIT2_LIB64_PATH := $(LIBGIT2_PATH)/lib64
LIBGIT2 := $(LIBGIT2_LIB_PATH)/libgit2.a
export CGO_ENABLED=1
export PKG_CONFIG_PATH=$(LIBGIT2_LIB_PATH)/pkgconfig
export LIBRARY_PATH=$(LIBGIT2_LIB_PATH)
export CGO_CFLAGS=-I$(LIBGIT2_PATH)/include -I$(LIBGIT2_PATH)/include/openssl
export CGO_LDFLAGS=$(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --libs --static --cflags libgit2 2>/dev/null)
# The pkg-config command will yield warning messages until libgit2 is downloaded.
ifeq ($(shell uname -s),Darwin)
GO_STATIC_FLAGS=-ldflags "-s -w" -tags 'netgo,osusergo,static_build'
endif
@ -46,8 +66,8 @@ ifeq ($(shell uname -s),Linux)
endif
# API (doc) generation utilities
CONTROLLER_GEN_VERSION ?= v0.16.1
GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113
CONTROLLER_GEN_VERSION ?= v0.7.0
GEN_API_REF_DOCS_VERSION ?= v0.3.0
# If gobin not set, create one on ./build and add to path.
ifeq (,$(shell go env GOBIN))
@ -69,7 +89,7 @@ ifeq ($(shell uname -s),Darwin)
ENVTEST_ARCH=amd64
endif
TEST_CRDS := internal/controller/testdata/crds
TEST_CRDS := controllers/testdata/crds
# Log level for `make run`
LOG_LEVEL ?= info
@ -109,17 +129,17 @@ ifeq ($(shell uname -s),Darwin)
endif
KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)"
test: tidy test-api test_deps generate fmt vet manifests api-docs install-envtest ## Run tests
test: $(LIBGIT2) tidy test-api test_deps generate fmt vet manifests api-docs install-envtest ## Run tests
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) \
go test $(GO_STATIC_FLAGS) $(GO_TEST_ARGS) ./... -coverprofile cover.out
test-api: ## Run api tests
cd api; go test $(GO_TEST_ARGS) ./... -coverprofile cover.out
manager: generate fmt vet ## Build manager binary
manager: $(LIBGIT2) generate fmt vet ## Build manager binary
go build -o $(BUILD_DIR)/bin/manager ./main.go
run: generate fmt vet manifests # Run against the configured Kubernetes cluster in ~/.kube/config
run: $(LIBGIT2) generate fmt vet manifests # Run against the configured Kubernetes cluster in ~/.kube/config
go run $(GO_STATIC_FLAGS) ./main.go --log-level=${LOG_LEVEL} --log-encoding=console
install: manifests ## Install CRDs into a cluster
@ -139,21 +159,20 @@ 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.18
rm -f go.sum; go mod tidy -compat=1.18
fmt: ## Run go fmt against code
go fmt ./...
cd api; go fmt ./...
vet: ## Run go vet against code
vet: $(LIBGIT2) ## Run go vet against code
go vet ./...
cd api; go vet ./...
@ -163,6 +182,8 @@ generate: controller-gen ## Generate code
docker-build: ## Build the Docker image
docker buildx build \
--build-arg LIBGIT2_IMG=$(LIBGIT2_IMG) \
--build-arg LIBGIT2_TAG=$(LIBGIT2_TAG) \
--platform=$(BUILD_PLATFORMS) \
-t $(IMG):$(TAG) \
$(BUILD_ARGS) .
@ -177,13 +198,21 @@ docker-deploy: ## Set the Docker image in-cluster
CONTROLLER_GEN = $(GOBIN)/controller-gen
.PHONY: controller-gen
controller-gen: ## Download controller-gen locally if necessary.
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION))
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0)
libgit2: $(LIBGIT2) ## Detect or download libgit2 library
COSIGN = $(GOBIN)/cosign
$(LIBGIT2):
$(call go-install-tool,$(COSIGN),github.com/sigstore/cosign/cmd/cosign@latest)
IMG=$(LIBGIT2_IMG) TAG=$(LIBGIT2_TAG) PATH=$(PATH):$(GOBIN) ./hack/install-libraries.sh
# Find or download gen-crd-api-reference-docs
GEN_CRD_API_REFERENCE_DOCS = $(GOBIN)/gen-crd-api-reference-docs
.PHONY: gen-crd-api-reference-docs
gen-crd-api-reference-docs:
$(call go-install-tool,$(GEN_CRD_API_REFERENCE_DOCS),github.com/ahmetb/gen-crd-api-reference-docs@$(GEN_API_REF_DOCS_VERSION))
$(call go-install-tool,$(GEN_CRD_API_REFERENCE_DOCS),github.com/ahmetb/gen-crd-api-reference-docs@v0.3.0)
ENVTEST_ASSETS_DIR=$(BUILD_DIR)/testbin
ENVTEST_KUBERNETES_VERSION?=latest
@ -198,7 +227,7 @@ setup-envtest: ## Download envtest-setup locally if necessary.
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)
# Build fuzzers used by oss-fuzz.
fuzz-build:
fuzz-build: $(LIBGIT2)
rm -rf $(shell pwd)/build/fuzz/
mkdir -p $(shell pwd)/build/fuzz/out/
@ -239,7 +268,20 @@ endef
update-attributions:
./hack/update-attributions.sh
verify:
verify: update-attributions fmt
ifneq ($(shell grep -o 'LIBGIT2_IMG ?= \w.*' Makefile | cut -d ' ' -f 3):$(shell grep -o 'LIBGIT2_TAG ?= \w.*' Makefile | cut -d ' ' -f 3), \
$(shell grep -o "LIBGIT2_IMG=\w.*" Dockerfile | cut -d'=' -f2):$(shell grep -o "LIBGIT2_TAG=\w.*" Dockerfile | cut -d'=' -f2))
@{ \
echo "LIBGIT2_IMG and LIBGIT2_TAG must match in both Makefile and Dockerfile"; \
exit 1; \
}
endif
ifneq ($(shell grep -o 'LIBGIT2_TAG ?= \w.*' Makefile | cut -d ' ' -f 3), $(shell grep -o "LIBGIT2_TAG=.*" tests/fuzz/oss_fuzz_build.sh | sed 's;LIBGIT2_TAG="$${LIBGIT2_TAG:-;;g' | sed 's;}";;g'))
@{ \
echo "LIBGIT2_TAG must match in both Makefile and tests/fuzz/oss_fuzz_build.sh"; \
exit 1; \
}
endif
ifneq (, $(shell git status --porcelain --untracked-files=no))
@{ \
echo "working directory is dirty:"; \
@ -251,3 +293,15 @@ endif
.PHONY: help
help: ## Display this help menu
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
# Creates an env file that can be used to load all image-automation-controller's
# dependencies this is handy when you want to run adhoc debug sessions on tests or
# start the controller in a new debug session.
env: $(LIBGIT2)
echo 'GO_ENABLED="1"' > $(BUILD_DIR)/.env
echo 'PKG_CONFIG_PATH="$(PKG_CONFIG_PATH)"' >> $(BUILD_DIR)/.env
echo 'LIBRARY_PATH="$(LIBRARY_PATH)"' >> $(BUILD_DIR)/.env
echo 'CGO_CFLAGS="$(CGO_CFLAGS)"' >> $(BUILD_DIR)/.env
echo 'CGO_LDFLAGS="$(CGO_LDFLAGS)"' >> $(BUILD_DIR)/.env
echo 'KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS)' >> $(BUILD_DIR)/.env
echo 'GIT_CONFIG_GLOBAL=/dev/null' >> $(BUILD_DIR)/.env

View File

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

View File

@ -21,5 +21,11 @@ guide](https://fluxcd.io/flux/guides/image-update/).
## How to work on it
For additional information on dependecies and how to contribute
please refer to [DEVELOPMENT.md](DEVELOPMENT.md).
The shared library `libgit2` needs to be installed to test or build
locally. The version required corresponds to the version of git2go
(which are Go bindings for libgit2), according to [this
table](https://github.com/libgit2/git2go#which-go-version-to-use).
See
https://github.com/fluxcd/source-controller/blob/main/DEVELOPMENT.md#installing-required-dependencies
for instructions on how to install `libgit2`.

View File

@ -1,32 +1,31 @@
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 v0.18.0
github.com/fluxcd/source-controller/api v0.32.1
k8s.io/apimachinery v0.25.4
sigs.k8s.io/controller-runtime v0.13.1
)
// Fix CVE-2022-32149
replace golang.org/x/text => golang.org/x/text v0.4.0
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/fluxcd/pkg/apis/acl v0.1.0 // 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.2.0 // indirect
golang.org/x/text v0.4.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-20221108210102-8e77b1f39fe2 // 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,115 +1,104 @@
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/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q=
github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8=
github.com/fluxcd/pkg/apis/meta v0.18.0 h1:s0LeulWcQ4DxVX6805vgDTxlA6bAYk+Lq1QHSnNdqLM=
github.com/fluxcd/pkg/apis/meta v0.18.0/go.mod h1:pYvXRFi1UKNNrGR34jw3uqOnMXw9X6dTkML8j5Z7tis=
github.com/fluxcd/source-controller/api v0.32.1 h1:PD8XEG4k5otsnusZZNEQ9XYY5udHoNgp5bX2yZHcb6k=
github.com/fluxcd/source-controller/api v0.32.1/go.mod h1:+DiGND4WSNdGkS7loPUroSarif6dHU4VlVgtLMRKCR8=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
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.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
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/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
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=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/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/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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
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.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0=
k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc=
k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo=
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-20221108210102-8e77b1f39fe2 h1:GfD9OzL11kvZN5iArC6oTS7RTj7oJOIfnislxYlqTj8=
k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.13.1 h1:tUsRCSJVM1QQOOeViGeX3GMT3dQF1eePPw6sEE3xSlg=
sigs.k8s.io/controller-runtime v0.13.1/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI=
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

@ -18,7 +18,7 @@ package v1beta1
import (
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
type GitSpec struct {
@ -85,20 +85,6 @@ type PushSpec struct {
// Branch specifies that commits should be pushed to the branch
// named. The branch is created using `.spec.checkout.branch` as the
// starting point, if it doesn't already exist.
// +optional
Branch string `json:"branch,omitempty"`
// Refspec specifies the Git Refspec to use for a push operation.
// If both Branch and Refspec are provided, then the commit is pushed
// to the branch and also using the specified refspec.
// For more details about Git Refspecs, see:
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
// +optional
Refspec string `json:"refspec,omitempty"`
// Options specifies the push options that are sent to the Git
// server when performing a push operation. For details, see:
// https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt
// +optional
Options map[string]string `json:"options,omitempty"`
// +required
Branch string `json:"branch"`
}

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.8.0
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,32 @@ 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.
description: "Branch to check out, defaults to 'master'
if no other field is defined. \n When GitRepositorySpec.GitImplementation
is set to 'go-git', a shallow clone of the specified
branch is performed."
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"
description: "Commit SHA to check out, takes precedence
over all reference fields. \n When GitRepositorySpec.GitImplementation
is set to 'go-git', this can be combined with Branch
to shallow clone the branch, in which the commit is
expected to exist."
type: string
semver:
description: SemVer tag expression to check out, takes
@ -96,9 +83,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 +98,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 +118,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 +168,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 +204,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 +255,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 +271,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 +295,12 @@ 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: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

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

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,5 +1,5 @@
//go:build gofuzz_libfuzzer
// +build gofuzz_libfuzzer
//go:build gofuzz
// +build gofuzz
/*
Copyright 2021 The Flux authors
@ -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,36 +29,36 @@ import (
"os/exec"
"path/filepath"
"sync"
"testing"
"time"
fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/fluxcd/image-automation-controller/controllers"
"github.com/fluxcd/image-automation-controller/pkg/update"
"github.com/fluxcd/pkg/gittestserver"
"github.com/fluxcd/pkg/runtime/testenv"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
gogit "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/manager"
image_reflectv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
"github.com/fluxcd/pkg/gittestserver"
"github.com/fluxcd/pkg/runtime/controller"
"github.com/fluxcd/pkg/runtime/testenv"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
image_automationv1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
"github.com/fluxcd/image-automation-controller/pkg/update"
image_automationv1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
image_reflectv1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
@ -85,9 +85,7 @@ 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}))
}))
})
@ -333,7 +331,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 +345,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 +360,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,623 @@
/*
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"
"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/v1beta1"
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/libgit2"
"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/v1beta2"
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"
const defaultMessageTemplate = `Update from image update automation`
const repoRefKey = ".spec.gitRepository"
const signingSecretKey = "git.asc"
// TemplateData is the type of the value given to the commit message
// template.
type TemplateData struct {
AutomationObject types.NamespacedName
Updated update.Result
}
// ImageUpdateAutomationReconciler reconciles a ImageUpdateAutomation object
type ImageUpdateAutomationReconciler struct {
client.Client
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)
}
gitImplementation := origin.Spec.GitImplementation
if goGitOnly, _ := r.features[features.ForceGoGitImplementation]; goGitOnly {
gitImplementation = sourcev1.GoGitImplementation
}
var gitClient repository.Client
switch gitImplementation {
case sourcev1.LibGit2Implementation:
clientOpts := []libgit2.ClientOption{libgit2.WithDiskStorage()}
if authOpts.Transport == git.HTTP {
clientOpts = append(clientOpts, libgit2.WithInsecureCredentialsOverHTTP())
}
gitClient, err = libgit2.NewClient(tmp, authOpts, clientOpts...)
case sourcev1.GoGitImplementation, "":
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage()}
forcePush, _ := features.Enabled(features.GitForcePushBranch)
if forcePush && pushBranch != ref.Branch {
clientOpts = append(clientOpts, gogit.WithForcePush())
}
if authOpts.Transport == git.HTTP {
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
}
gitClient, err = gogit.NewClient(tmp, authOpts, clientOpts...)
default:
err = fmt.Errorf("failed to create git client; referred GitRepository has invalid implementation: %s", gitImplementation)
}
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
}
// 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)
}
}
if result, err := updateAccordingToSetters(ctx, tracelog, manifestsPath, policies.Items); err != nil {
return failWithError(err)
} else {
templateValues.Updated = result
}
default:
log.Info("no update strategy given in the spec")
// no sense rescheduling until this resource changes
r.event(ctx, auto, 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)
}
// 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.
if 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),
); err != nil {
if err != git.ErrNoStagedFiles {
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)
}
return entities[0], 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, path string, policies []imagev1_reflect.ImagePolicy) (update.Result, error) {
return update.UpdateWithSetters(tracelog, path, path, 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

@ -0,0 +1,19 @@
package controllers
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"
@ -24,18 +24,18 @@ import (
"testing"
"time"
git2go "github.com/libgit2/git2go/v34"
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"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
"github.com/fluxcd/pkg/git/libgit2/transport"
"github.com/fluxcd/pkg/runtime/testenv"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
"github.com/fluxcd/image-automation-controller/internal/features"
// +kubebuilder:scaffold:imports
)
@ -46,9 +46,8 @@ import (
// Gomega.
var (
k8sClient client.Client
testEnv *testenv.Environment
ctx = ctrl.SetupSignalHandler()
testEnv *testenv.Environment
ctx = ctrl.SetupSignalHandler()
)
func init() {
@ -56,43 +55,41 @@ func init() {
}
func TestMain(m *testing.M) {
mustHaveNoThreadSupport()
utilruntime.Must(imagev1_reflect.AddToScheme(scheme.Scheme))
utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme))
utilruntime.Must(imagev1.AddToScheme(scheme.Scheme))
if err := transport.InitManagedTransport(); err != nil {
panic(fmt.Sprintf("failed to initialize libgit2 managed transport: %v", err))
}
code := runTestsWithFeatures(m, nil)
if code != 0 {
fmt.Println("failed with default feature values")
os.Exit(code)
}
code = runTestsWithFeatures(m, map[string]bool{
features.ForceGoGitImplementation: false,
})
os.Exit(code)
}
func runTestsWithFeatures(m *testing.M, feats map[string]bool) int {
testEnv = testenv.New(
testenv.WithCRDPath(
filepath.Join("..", "..", "config", "crd", "bases"),
filepath.Join("testdata", "crds"),
),
testenv.WithMaxConcurrentReconciles(2),
)
var err error
// Initialize a cacheless client for tests that need the latest objects.
k8sClient, err = client.New(testEnv.Config, client.Options{Scheme: scheme.Scheme})
if err != nil {
panic(fmt.Sprintf("failed to create k8s client: %v", err))
}
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{
RateLimiter: controller.GetDefaultRateLimiter(),
}); err != nil {
Client: testEnv,
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
features: feats,
}).SetupWithManager(testEnv, ImageUpdateAutomationReconcilerOptions{}); err != nil {
panic(fmt.Sprintf("failed to start ImageUpdateAutomationReconciler: %v", err))
}
@ -113,3 +110,22 @@ func runTestsWithFeatures(m *testing.M, feats map[string]bool) int {
return code
}
// This provides a regression assurance for image-automation-controller/#339.
// Validates that:
// - libgit2 was built with no support for threads.
// - git2go accepts libgit2 built with no support for threads.
//
// The logic below does the validation of the former, whilst
// referring to git2go forces its init() execution, which is
// where any validation to that effect resides.
//
// git2go does not support threadless libgit2 by default,
// hence a fork is being used which disables such validation.
//
// TODO: extract logic into pkg.
func mustHaveNoThreadSupport() {
if git2go.Features()&git2go.FeatureThreads != 0 {
panic("libgit2 must not be build with thread support")
}
}

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

1
controllers/testdata/brokenlink/bar.yaml vendored Symbolic link
View File

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

1759
controllers/update_test.go Normal file

File diff suppressed because it is too large Load Diff

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>
@ -207,9 +207,7 @@ string
<td>
<code>ref</code><br>
<em>
<a href="https://pkg.go.dev/github.com/fluxcd/source-controller/api/v1#GitRepositoryRef">
Source /v1.GitRepositoryRef
</a>
Source /v1beta2.GitRepositoryRef
</em>
</td>
<td>
@ -360,7 +358,7 @@ other kinds of source allowed.</p>
<td>
<code>interval</code><br>
<em>
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
<a href="https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Kubernetes meta/v1.Duration
</a>
</em>
@ -468,7 +466,7 @@ other kinds of source allowed.</p>
<td>
<code>interval</code><br>
<em>
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
<a href="https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Kubernetes meta/v1.Duration
</a>
</em>
@ -598,7 +596,7 @@ int64
<td>
<code>ReconcileRequestStatus</code><br>
<em>
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#ReconcileRequestStatus">
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#ReconcileRequestStatus">
github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
</a>
</em>
@ -638,42 +636,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>
@ -699,7 +666,7 @@ server when performing a push operation. For details, see:
<td>
<code>secretRef</code><br>
<em>
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</a>
</em>

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.
@ -113,8 +111,9 @@ To be able to commit changes back, the referenced `GitRepository` object must re
with write access; e.g., if using a GitHub deploy key, "Allow write access" should be checked when
creating it. Only the `url`, `ref`, and `secretRef` fields of the `GitRepository` are used.
The [`gitImplementation` field][source-docs] in the referenced `GitRepository` is ignored. All
reconciliations are executed using the `go-git` implementation.
The [`gitImplementation` field][source-docs] in the referenced `GitRepository` is ignored. The
automation controller cannot use shallow clones or submodules, so there is no reason to use the
go-git implementation rather than libgit2.
Other fields particular to how the Git repository is used are in the `git` field, [described
below](#git-specific-specification).
@ -170,9 +169,6 @@ When `checkout` is given, it overrides the analogous field in the `GitRepository
in `.spec.sourceRef`. You would use this to put automation commits on a different branch than that
you are syncing, for example.
By default the controller will only do shallow clones, but this can be disabled by starting the controller
with `--feature-gates=GitShallowClone=false`.
### Commit
The `.spec.git.commit` field gives details to use when making a commit to push to the Git repository:
@ -228,21 +224,7 @@ will result in commits with the author `Fluxbot <flux@example.com>`.
The optional `signingKey` field can be used to provide a key to sign commits with. It holds a
reference to a secret, which is expected to have a file called `git.asc` containing an
ASCII-armoured PGP key. If the private key is protected by a password, you can specify the same
in the secret using the `passphrase` key.
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: signing-key
namespace: default
stringData:
git.asc: |
<ARMOR ENCODED PGP KEY>
passphrase: <private-key-passphrase>
```
ASCII-armoured PGP key.
The `messageTemplate` field is a string which will be used as a template for the commit message. If
empty, there is a default message; but you will likely want to provide your own, especially if you
@ -266,7 +248,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 +380,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,51 +390,19 @@ type PushSpec struct {
// Branch specifies that commits should be pushed to the branch
// named. The branch is created using `.spec.checkout.branch` as the
// starting point, if it doesn't already exist.
// +optional
Branch string `json:"branch,omitempty"`
// Refspec specifies the Git Refspec to use for a push operation.
// If both Branch and Refspec are provided, then the commit is pushed
// to the branch and also using the specified refspec.
// For more details about Git Refspecs, see:
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
// +optional
Refspec string `json:"refspec,omitempty"`
// Options specifies the push options that are sent to the Git
// server when performing a push operation. For details, see:
// https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt
Options map[string]string `json:"options,omitempty"`
// +required
Branch string `json:"branch"`
}
```
If `.push` is not present, commits are made on the branch given in `.spec.git.checkout.branch` and
If `push` is not present, commits are made on the branch given in `.spec.git.checkout.branch` and
pushed to the same branch at the origin. If `.spec.git.checkout` is not present, it will fall back
to the branch given in the `GitRepository` referenced by `.spec.sourceRef`. If none of these yield a
branch name, the automation will fail.
If `.push.refspec` is present, the refspec specified is used to perform the push operation.
An example of a valid refspec is `refs/heads/branch:refs/heads/branch`. This allows users to
push to an arbitary destination reference.
If `.push.branch` is present, the specified branch is pushed to at the origin. The branch
When `push` is present, the `branch` field specifies a branch to push to at the origin. The branch
will be created locally if it does not already exist, starting from the checkout branch. If it does
already exist, it will be overwritten with the cloned version plus the changes made by the
controller. Alternatively, force push can be disabled by starting the controller with `--feature-gates=GitForcePushBranch=false`,
in which case the updates will be calculated on top of any commits already on the push branch.
Note that without force push in push branches, if the target branch is stale, the controller may not
be able to conclude the operation and will consistently fail until the branch is either deleted or
refreshed.
If both `.push.refspec` and `.push.branch` are specified, then the reconciler will perform
two push operations, one to the specified branch and another using the specified refspec.
This is particularly useful for working with Gerrit servers. For more information about this,
please refer to the [Gerrit](#gerrit) section.
**Note:** If both `.push.refspec` and `.push.branch` are essentially equal to
each other (for e.g.: `.push.refspec: refs/heads/main:refs/heads/main` and
`.push.branch: main`), then the reconciler might fail to perform the second push
operation and error out with an `already up-to-date` error.
already exist, updates will be calculated on top of any commits already on the branch.
In the following snippet, updates will be pushed as commits to the branch `auto`, and when that
branch does not exist at the origin, it will be created locally starting from the branch `main`, and
@ -469,117 +418,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 +669,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

279
go.mod
View File

@ -1,180 +1,177 @@
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
)
// Flux has its own git2go fork to enable changes in behaviour for improved
// reliability.
//
// For more information refer to:
// - fluxcd/image-automation-controller/#339.
// - libgit2/git2go#918.
replace github.com/libgit2/git2go/v34 => github.com/fluxcd/git2go/v34 v34.0.0
// This lets us use `go-billy/util.Walk()`, as this function hasn't been released
// in a tagged version yet:
// https://github.com/go-git/go-billy/blob/e0768be422ff616fc042d1d62bfa65962f716ad8/util/walk.go#L59
replace github.com/go-git/go-billy/v5 => github.com/go-git/go-billy/v5 v5.3.2-0.20210603175951-e0768be422ff
// Use Flux's version of go-git, which allows clones with multi_ack and multi_ack_detailed.
replace github.com/go-git/go-git/v5 => github.com/fluxcd/go-git/v5 v5.0.0-20221019082644-b33dae782906
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
github.com/AdaLogics/go-fuzz-headers v0.0.0-20221103172237-443f56ff4ba8
github.com/Masterminds/sprig/v3 v3.2.2
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4
github.com/cyphar/filepath-securejoin v0.2.3
github.com/fluxcd/image-automation-controller/api v0.26.1
github.com/fluxcd/image-reflector-controller/api v0.23.0
github.com/fluxcd/pkg/apis/acl v0.1.0
github.com/fluxcd/pkg/apis/event v0.2.0
github.com/fluxcd/pkg/apis/meta v0.18.0
github.com/fluxcd/pkg/git v0.7.0
github.com/fluxcd/pkg/git/gogit v0.2.0
github.com/fluxcd/pkg/git/libgit2 v0.3.0
github.com/fluxcd/pkg/gittestserver v0.8.0
github.com/fluxcd/pkg/runtime v0.24.0
github.com/fluxcd/pkg/ssh v0.7.0
github.com/fluxcd/source-controller/api v0.32.1
github.com/go-git/go-billy/v5 v5.3.1
github.com/go-git/go-git/v5 v5.4.2
github.com/go-logr/logr v1.2.3
github.com/google/go-containerregistry v0.12.1
github.com/libgit2/git2go/v34 v34.0.0
github.com/onsi/gomega v1.24.1
github.com/otiai10/copy v1.9.0
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.3.0
k8s.io/api v0.25.4
k8s.io/apimachinery v0.25.4
k8s.io/client-go v0.25.4
k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70
sigs.k8s.io/controller-runtime v0.13.1
sigs.k8s.io/kustomize/kyaml v0.13.9
)
// Fix CVE-2022-32149
replace golang.org/x/text => golang.org/x/text v0.4.0
// Fix CVE-2022-1996 (for v2, Go Modules incompatible)
replace github.com/emicklei/go-restful => github.com/emicklei/go-restful v2.16.0+incompatible
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
cloud.google.com/go/compute v1.10.0 // 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.1.1 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/acomagu/bufpipe v1.0.3 // 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.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 // 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/go-git/v5 v5.0.0-20221104190732-329fd6659b10 // indirect
github.com/fluxcd/pkg/http/transport v0.1.0 // indirect
github.com/fluxcd/pkg/version v0.2.0 // 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.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // 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.2 // 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/inconshreveable/mousetrap v1.1.0 // indirect
github.com/hashicorp/go-hclog v1.3.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // 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-20221105221325-4eb28fa6025c // 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/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/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/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.2.0 // 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.6.1 // indirect
github.com/xanzy/ssh-agent v0.3.2 // 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.23.0 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/oauth2 v0.2.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/term v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.2.0 // indirect
golang.org/x/tools v0.2.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // 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
gotest.tools/v3 v3.1.0 // indirect
k8s.io/apiextensions-apiserver v0.25.4 // indirect
k8s.io/cli-runtime v0.25.4 // indirect
k8s.io/component-base v0.25.4 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kubectl v0.25.4 // indirect
k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2 // 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
)

1108
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
"externalPackages": [
{
"typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$",
"docsURLTemplate": "https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration"
"docsURLTemplate": "https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration"
},
{
"typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/",
@ -17,15 +17,15 @@
},
{
"typeMatchPrefix": "^github.com/fluxcd/pkg/runtime/dependency\\.CrossNamespaceDependencyReference$",
"docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/pkg/runtime/dependency#CrossNamespaceDependencyReference"
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/runtime/dependency#CrossNamespaceDependencyReference"
},
{
"typeMatchPrefix": "^github.com/fluxcd/pkg/apis/meta",
"docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#{{ .TypeIdentifier }}"
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/apis/meta#{{ .TypeIdentifier }}"
},
{
"typeMatchPrefix": "^github.com/fluxcd/source-controller/api/v1",
"docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/source-controller/api/v1#{{ .TypeIdentifier }}"
"typeMatchPrefix": "^github.com/fluxcd/source-controller/api/v1beta1",
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/source-controller/api/v1beta2#{{ .TypeIdentifier }}"
}
],
"typeDisplayNamePrefixOverrides": {

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.

66
hack/extract-libraries.sh Executable file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -euxo pipefail
IMG_TAG="${IMG_TAG:-.}"
function extract(){
PLATFORM=$1
DIR=$2
id=$(docker create --platform="${PLATFORM}" "${IMG_TAG}")
docker cp "${id}":/usr/local - > output.tar.gz
docker rm -v "${id}"
tar -xf output.tar.gz "local/${DIR}"
rm output.tar.gz
}
function setup() {
PLATFORM=$1
DIR=$2
extract "${PLATFORM}" "${DIR}"
NEW_DIR="$(/bin/pwd)/build/libgit2"
INSTALLED_DIR="/usr/local/${DIR}"
mkdir -p "./build"
# Make a few movements to account for the change in
# behaviour in tar between MacOS and Linux
mv "local/${DIR}/" "libgit2"
rm -rf "local"
mv "libgit2/" "./build/"
# Update the prefix paths included in the .pc files.
# This will make it easier to update to the location in which they will be used.
if [[ $OSTYPE == 'darwin'* ]]; then
# sed has a sight different behaviour in MacOS
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "" "s;${INSTALLED_DIR};${NEW_DIR};g" {}
else
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "s;${INSTALLED_DIR};${NEW_DIR};g" {}
fi
}
function setup_current() {
if [ -d "./build/libgit2" ]; then
echo "Skipping libgit2 setup as it already exists"
exit 0
fi
DIR="x86_64-alpine-linux-musl"
PLATFORM="linux/amd64"
if [[ "$(uname -m)" == armv7* ]]; then
DIR="armv7-alpine-linux-musleabihf"
PLATFORM="linux/arm/v7"
elif [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then
DIR="aarch64-alpine-linux-musl"
PLATFORM="linux/arm64"
fi
setup "${PLATFORM}" "${DIR}"
}
setup_current

157
hack/install-libraries.sh Executable file
View File

@ -0,0 +1,157 @@
#!/usr/bin/env bash
set -euxo pipefail
IMG="${IMG:-}"
TAG="${TAG:-}"
IMG_TAG="${IMG}:${TAG}"
DOWNLOAD_URL="https://github.com/fluxcd/golang-with-libgit2/releases/download/${TAG}"
SKIP_COSIGN_VERIFICATION="${SKIP_COSIGN_VERIFICATION:-false}"
TMP_DIR=$(mktemp -d)
function cleanup(){
rm -rf "${TMP_DIR}"
}
trap cleanup EXIT
fatal() {
echo '[ERROR] ' "$@" >&2
exit 1
}
download() {
[[ $# -eq 2 ]] || fatal 'download needs exactly 2 arguments'
curl -o "$1" -sfL "$2"
[[ $? -eq 0 ]] || fatal 'Download failed'
}
download_files() {
[[ $# -eq 1 ]] || fatal 'download_files needs exactly 1 arguments'
FILE_NAMES="checksums.txt checksums.txt.sig checksums.txt.pem $1"
for FILE_NAME in ${FILE_NAMES}; do
download "${TMP_DIR}/${FILE_NAME}" "${DOWNLOAD_URL}/${FILE_NAME}"
done
}
cosign_verify(){
[[ $# -eq 3 ]] || fatal 'cosign_verify needs exactly 3 arguments'
COSIGN_EXPERIMENTAL=1 cosign verify-blob --cert "$1" --signature "$2" "$3"
[[ $? -eq 0 ]] || fatal 'signature verification failed'
}
assure_provenance() {
[[ $# -eq 1 ]] || fatal 'assure_provenance needs exactly 1 arguments'
if "${SKIP_COSIGN_VERIFICATION}"; then
echo 'Skipping cosign verification...'
else
cosign_verify "${TMP_DIR}/checksums.txt.pem" \
"${TMP_DIR}/checksums.txt.sig" \
"${TMP_DIR}/checksums.txt"
fi
pushd "${TMP_DIR}" || exit
if command -v sha256sum; then
grep "$1" "checksums.txt" | sha256sum --check
else
grep "$1" "checksums.txt" | shasum -a 256 --check
fi
popd || exit
[[ $? -eq 0 ]] || fatal 'integrity verification failed'
}
extract_libraries(){
[[ $# -eq 2 ]] || fatal 'extract_libraries needs exactly 2 arguments'
tar -xf "${TMP_DIR}/$1"
rm "${TMP_DIR}/$1"
mv "${2}" "${TAG}"
mv "${TAG}/" "./build/libgit2"
}
fix_pkgconfigs(){
DIR="$1"
NEW_DIR="$(/bin/pwd)/build/libgit2/${TAG}"
# Update the prefix paths included in the .pc files.
if [[ $OSTYPE == 'darwin'* ]]; then
INSTALLED_DIR="/Users/runner/work/golang-with-libgit2/golang-with-libgit2/build/${DIR}"
# This will make it easier to update to the location in which they will be used.
# sed has a sight different behaviour in MacOS
# NB: Some macOS users may override their sed with gsed. If gsed is the PATH, use that instead.
if command -v gsed &> /dev/null; then
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} gsed -i "s;${INSTALLED_DIR};${NEW_DIR};g" {}
else
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "" "s;${INSTALLED_DIR};${NEW_DIR};g" {}
fi
else
INSTALLED_DIR="/home/runner/work/golang-with-libgit2/golang-with-libgit2/build/${DIR}"
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "s;${INSTALLED_DIR};${NEW_DIR};g" {}
fi
}
extract_from_image(){
PLATFORM=$1
DIR=$2
id=$(docker create --platform="${PLATFORM}" "${IMG_TAG}" sh)
docker cp "${id}":/usr/local - > output.tar.gz
docker rm -v "${id}"
tar -xf output.tar.gz "local/${DIR}"
rm output.tar.gz
NEW_DIR="$(/bin/pwd)/build/libgit2/${TAG}"
INSTALLED_DIR="/usr/local/${DIR}"
mv "local/${DIR}" "${TAG}"
rm -rf "local"
mv "${TAG}/" "./build/libgit2"
# Update the prefix paths included in the .pc files.
# This will make it easier to update to the location in which they will be used.
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "s;${INSTALLED_DIR};${NEW_DIR};g" {}
}
install_libraries(){
if [ -d "./build/libgit2/${TAG}" ]; then
echo "Skipping: libgit2 ${TAG} already installed"
exit 0
fi
mkdir -p "./build/libgit2"
# Linux ARM support is still based on the container image libraries.
if [[ $OSTYPE == 'linux'* ]]; then
if [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then
extract_from_image "linux/arm64" "aarch64-alpine-linux-musl"
fix_pkgconfigs "aarch64-alpine-linux-musl"
exit 0
fi
fi
FILE_NAME="linux-$(uname -m)-libgit2-only.tar.gz"
DIR="linux-libgit2-only"
if [[ $OSTYPE == 'darwin'* ]]; then
FILE_NAME="darwin-libgit2-only.tar.gz"
DIR="darwin-libgit2-only"
fi
download_files "${FILE_NAME}"
assure_provenance "${FILE_NAME}"
extract_libraries "${FILE_NAME}" "${DIR}"
fix_pkgconfigs "${DIR}"
}
install_libraries

73
hack/update-attributions.sh Executable file
View File

@ -0,0 +1,73 @@
#!/usr/bin/env bash
set -eoux pipefail
SPLIT="***"
cat <<EOF > ATTRIBUTIONS.md
# Attributions
This application uses Open Source components. You can find the source
code of their open source projects along with license information below.
We acknowledge and are grateful to these developers for their contributions
to open source.
## libssh2
Libssh2 was obtained in source-code form from its github repository:
https://github.com/libssh2/libssh2/
No changes were made to its original source code.
Copyright notice (https://raw.githubusercontent.com/libssh2/libssh2/master/COPYING):
$(curl --max-time 5 -L https://raw.githubusercontent.com/libssh2/libssh2/master/COPYING)
${SPLIT}
## libgit2
Libgit2 was obtained in source-code form from its github repository:
https://github.com/libgit2/libgit2/
No changes were made to its original source code.
Copyright notice (https://raw.githubusercontent.com/libgit2/libgit2/main/COPYING):
$(curl --max-time 5 -L https://raw.githubusercontent.com/libgit2/libgit2/main/COPYING)
${SPLIT}
## zlib
Zlib was obtained in binary form via official distribution channels.
No changes were made to its original source code.
Copyright notice (https://zlib.net/zlib_license.html):
/* zlib.h -- interface of the 'zlib' general purpose compression library
version 1.2.11, January 15th, 2017
Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Jean-loup Gailly Mark Adler
jloup@gzip.org madler@alumni.caltech.edu
*/
EOF

View File

@ -1,24 +0,0 @@
/*
Copyright 2025 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package constants
const (
// SetterShortHand is a shorthand that can be used to mark
// setters; instead of
// # { "$ref": "#/definitions/
SetterShortHand = "$imagepolicy"
)

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

@ -15,34 +15,23 @@ limitations under the License.
*/
// Package features sets the feature gates that
// image-automation-controller supports, and their default
// source-controller supports, and their default
// 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
// are configured.
GitForcePushBranch = "GitForcePushBranch"
// GitShallowClone enables the use of shallow clones when pulling source from
// Git repositories.
GitShallowClone = "GitShallowClone"
// GitAllBranchReferences enables the download of all branch head references
// when push branches are configured. When enabled fixes fluxcd/flux2#3384.
GitAllBranchReferences = "GitAllBranchReferences"
// GitSparseCheckout enables the use of sparse checkout when pulling source from
// Git repositories.
GitSparseCheckout = "GitSparseCheckout"
// CacheSecretsAndConfigMaps controls whether Secrets and ConfigMaps should
// be cached.
// ForceGoGitImplementation ignores the value set for gitImplementation
// of a GitRepository object and ensures that go-git is used for all git operations.
//
// When enabled, it will cache both object types, resulting in increased
// memory usage and cluster-wide RBAC permissions (list and watch).
CacheSecretsAndConfigMaps = "CacheSecretsAndConfigMaps"
// When enabled, libgit2 won't be initialized, nor will any git2go cgo
// code be called.
ForceGoGitImplementation = "ForceGoGitImplementation"
)
var features = map[string]bool{
@ -50,28 +39,12 @@ var features = map[string]bool{
// opt-out from v0.27
GitForcePushBranch: true,
// GitShallowClone
// opt-out from v0.28
GitShallowClone: true,
// GitAllBranchReferences
// opt-out from v0.28
GitAllBranchReferences: true,
// GitSparseCheckout
// opt-in from v0.42
GitSparseCheckout: false,
// CacheSecretsAndConfigMaps
// opt-in from v0.29
CacheSecretsAndConfigMaps: false,
// ForceGoGitImplementation
// opt-out from v0.27
ForceGoGitImplementation: true,
}
func init() {
auth.SetFeatureGates(features)
}
// FeatureGates contains a list of all supported feature gates and
// DefaultFeatureGates contains a list of all supported feature gates and
// their default values.
func FeatureGates() map[string]bool {
return features

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

143
main.go
View File

@ -21,22 +21,13 @@ import (
"os"
flag "github.com/spf13/pflag"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
ctrlcfg "sigs.k8s.io/controller-runtime/pkg/config"
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
"github.com/fluxcd/pkg/auth"
cache "github.com/fluxcd/pkg/cache"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
"github.com/fluxcd/pkg/runtime/acl"
"github.com/fluxcd/pkg/runtime/client"
helper "github.com/fluxcd/pkg/runtime/controller"
@ -44,22 +35,24 @@ 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"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/pkg/git"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
"github.com/fluxcd/image-automation-controller/internal/features"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/libgit2/transport"
// +kubebuilder:scaffold:imports
"github.com/fluxcd/image-automation-controller/internal/controller"
"github.com/fluxcd/image-automation-controller/controllers"
)
const (
controllerName = "image-automation-controller"
// recoverPanic indicates whether panic caused by reconciles should be recovered.
recoverPanic = true
)
var (
@ -76,10 +69,6 @@ func init() {
}
func main() {
const (
tokenCacheDefaultMaxSize = 100
)
var (
metricsAddr string
eventsAddr string
@ -90,14 +79,15 @@ func main() {
leaderElectionOptions leaderelection.Options
rateLimiterOptions helper.RateLimiterOptions
featureGates feathelper.FeatureGates
watchOptions helper.WatchOptions
watchAllNamespaces bool
concurrent int
tokenCacheOptions cache.TokenFlags
)
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&eventsAddr, "events-addr", "", "The address of the events receiver.")
flag.StringVar(&healthAddr, "health-addr", ":9440", "The address the health endpoint binds to.")
flag.BoolVar(&watchAllNamespaces, "watch-all-namespaces", true,
"Watch for custom resources in all namespaces, if set to false it will only watch the runtime namespace.")
flag.IntVar(&concurrent, "concurrent", 4, "The number of concurrent resource reconciles.")
flag.StringSliceVar(&git.KexAlgos, "ssh-kex-algos", []string{},
"The list of key exchange algorithms to use for ssh connections, arranged from most preferred to the least.")
@ -110,12 +100,11 @@ func main() {
aclOptions.BindFlags(flag.CommandLine)
rateLimiterOptions.BindFlags(flag.CommandLine)
featureGates.BindFlags(flag.CommandLine)
watchOptions.BindFlags(flag.CommandLine)
tokenCacheOptions.BindFlags(flag.CommandLine, tokenCacheDefaultMaxSize)
flag.Parse()
logger.SetLogger(logger.NewLogger(logOptions))
log := logger.NewLogger(logOptions)
ctrl.SetLogger(log)
err := featureGates.WithLogger(setupLog).
SupportedFeatures(features.FeatureGates())
@ -124,84 +113,32 @@ 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 {
if !watchAllNamespaces {
watchNamespace = os.Getenv("RUNTIME_NAMESPACE")
}
var disableCacheFor []ctrlclient.Object
shouldCache, err := features.Enabled(features.CacheSecretsAndConfigMaps)
if err != nil {
setupLog.Error(err, "unable to check feature gate "+features.CacheSecretsAndConfigMaps)
os.Exit(1)
}
if !shouldCache {
disableCacheFor = append(disableCacheFor, &corev1.Secret{}, &corev1.ConfigMap{})
}
restConfig := client.GetConfigOrDie(clientOptions)
watchSelector, err := helper.GetWatchSelector(watchOptions)
if err != nil {
setupLog.Error(err, "unable to configure watch label selector for manager")
os.Exit(1)
}
leaderElectionID := fmt.Sprintf("%s-leader-election", controllerName)
if watchOptions.LabelSelector != "" {
leaderElectionID = leaderelection.GenerateID(leaderElectionID, watchOptions.LabelSelector)
}
mgrConfig := ctrl.Options{
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
HealthProbeBindAddress: healthAddr,
Port: 9443,
LeaderElection: leaderElectionOptions.Enable,
LeaderElectionReleaseOnCancel: leaderElectionOptions.ReleaseOnCancel,
LeaseDuration: &leaderElectionOptions.LeaseDuration,
RenewDeadline: &leaderElectionOptions.RenewDeadline,
RetryPeriod: &leaderElectionOptions.RetryPeriod,
LeaderElectionID: leaderElectionID,
Client: ctrlclient.Options{
Cache: &ctrlclient.CacheOptions{
DisableFor: disableCacheFor,
},
},
Cache: ctrlcache.Options{
ByObject: map[ctrlclient.Object]ctrlcache.ByObject{
&imagev1.ImageUpdateAutomation{}: {Label: watchSelector},
},
},
Metrics: metricsserver.Options{
BindAddress: metricsAddr,
ExtraHandlers: pprof.GetHandlers(),
},
Controller: ctrlcfg.Controller{
RecoverPanic: pointer.Bool(true),
MaxConcurrentReconciles: concurrent,
},
}
if watchNamespace != "" {
mgrConfig.Cache.DefaultNamespaces = map[string]ctrlcache.Config{
watchNamespace: ctrlcache.Config{},
}
}
mgr, err := ctrl.NewManager(restConfig, mgrConfig)
LeaderElectionID: fmt.Sprintf("%s-leader-election", controllerName),
Namespace: watchNamespace,
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
probes.SetupChecks(mgr, setupLog)
pprof.SetupHandlers(mgr, setupLog)
var eventRecorder *events.Recorder
if eventRecorder, err = events.NewRecorder(mgr, ctrl.Log, eventsAddr, controllerName); err != nil {
@ -209,40 +146,32 @@ 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)
}
// +kubebuilder:scaffold:builder
if gogitOnly, _ := features.Enabled(features.ForceGoGitImplementation); !gogitOnly {
if err = transport.InitManagedTransport(); err != nil {
setupLog.Error(err, "unable to initialize libgit2 managed transport")
os.Exit(1)
}
}
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"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
)
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,9 +28,8 @@ 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"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
)
func TestUpdateWithSetters(t *testing.T) {
@ -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,16 +1,9 @@
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
RUN apt-get update && apt-get install -y cmake pkg-config
COPY ./ $GOPATH/src/github.com/fluxcd/image-automation-controller/
RUN wget https://raw.githubusercontent.com/google/oss-fuzz/master/projects/fluxcd/build.sh -O $SRC/build.sh
COPY ./tests/fuzz/oss_fuzz_build.sh $SRC/build.sh
COPY tests/fuzz/compile_native_go_fuzzer /usr/local/bin/
WORKDIR $SRC

View File

@ -1,82 +0,0 @@
# fuzz testing
Flux is part of Google's [oss fuzz] program which provides continuous fuzzing for
open source projects.
The long running fuzzing execution is configured in the [oss-fuzz repository].
Shorter executions are done on a per-PR basis, configured as a [github workflow].
### Testing locally
Build fuzzers:
```bash
make fuzz-build
```
All fuzzers will be built into `./build/fuzz/out`.
Smoke test fuzzers:
All the fuzzers will be built and executed once, to ensure they are fully functional.
```bash
make fuzz-smoketest
```
Run fuzzer locally:
```bash
./build/fuzz/out/fuzz_conditions_match
```
Run fuzzer inside a container:
```bash
docker run --rm -ti \
-v "$(pwd)/build/fuzz/out":/out \
gcr.io/oss-fuzz/fluxcd \
/out/fuzz_conditions_match
```
### Caveats of creating oss-fuzz compatible tests
#### Segregate fuzz tests
OSS-Fuzz does not properly support mixed `*_test.go` files, in which there is a combination
of fuzz and non-fuzz tests. To mitigate this problem, ensure your fuzz tests are not in the
same file as other Go tests. As a pattern, call your fuzz test files `*_fuzz_test.go`.
#### Build tags to avoid conflicts when running Go tests
Due to the issue above, code duplication will occur when creating fuzz tests that rely on
helper functions that are shared with other tests. To avoid build issues, add a conditional
build tag at the top of the `*_fuzz_test.go` file:
```go
//go:build gofuzz_libfuzzer
// +build gofuzz_libfuzzer
```
The build tag above is set at [go-118-fuzz-build].
At this point in time we can't pass on specific tags from [compile_native_go_fuzzer].
### Running oss-fuzz locally
The `make fuzz-smoketest` is meant to be an easy way to reproduce errors that may occur
upstream. If our checks ever run out of sync with upstream, the upstream tests can be
executed locally with:
```
git clone --depth 1 https://github.com/google/oss-fuzz
cd oss-fuzz
python infra/helper.py build_image fluxcd
python infra/helper.py build_fuzzers --sanitizer address --architecture x86_64 fluxcd
python infra/helper.py check_build --sanitizer address --architecture x86_64 fluxcd
```
For latest info on testing oss-fuzz locally, refer to the [upstream guide].
[oss fuzz]: https://github.com/google/oss-fuzz
[oss-fuzz repository]: https://github.com/google/oss-fuzz/tree/master/projects/fluxcd
[github workflow]: .github/workflows/cifuzz.yaml
[upstream guide]: https://google.github.io/oss-fuzz/getting-started/new-project-guide/#testing-locally
[go-118-fuzz-build]: https://github.com/AdamKorcz/go-118-fuzz-build/blob/b2031950a318d4f2dcf3ec3e128f904d5cf84623/main.go#L40
[compile_native_go_fuzzer]: https://github.com/google/oss-fuzz/blob/c2d827cb78529fdc757c9b0b4fea0f1238a54814/infra/base-images/base-builder/compile_native_go_fuzzer#L32

View File

@ -0,0 +1,100 @@
#!/bin/bash -eux
# Copyright 2022 Google LLC
#
# 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.
#
################################################################################
# This is a copy of the upstream script which is only needed to link
# additional static libraries. Orignal source:
#
# https://github.com/google/oss-fuzz/blob/81326f0a39eadebfa9b7a98aa9f0553707875696/infra/base-images/base-builder/compile_go_fuzzer
# Rewrites a copy of the fuzzer to allow for
# libFuzzer instrumentation.
function rewrite_go_fuzz_harness() {
fuzzer_filename=$1
fuzz_function=$2
# Create a copy of the fuzzer to not modify the existing fuzzer.
cp $fuzzer_filename "${fuzzer_filename}"_fuzz_.go
mv $fuzzer_filename /tmp/
fuzzer_fn="${fuzzer_filename}"_fuzz_.go
# Replace *testing.F with *go118fuzzbuildutils.F.
echo "replacing *testing.F"
sed -i "s/func $fuzz_function(\([a-zA-Z0-9]*\) \*testing\.F)/func $fuzz_function(\1 \*go118fuzzbuildutils\.F)/g" "${fuzzer_fn}"
# Import https://github.com/AdamKorcz/go-118-fuzz-build.
# This changes the line numbers from the original fuzzer.
addimport -path "${fuzzer_fn}"
}
function build_native_go_fuzzer() {
fuzzer=$1
function=$2
path=$3
tags="-tags gofuzz"
if [[ $SANITIZER = *coverage* ]]; then
echo "here we perform coverage build"
fuzzed_package=`go list $tags -f '{{.Name}}' $path`
abspath=`go list $tags -f {{.Dir}} $path`
cd $abspath
cp $GOPATH/native_ossfuzz_coverage_runner.go ./"${function,,}"_test.go
sed -i -e 's/FuzzFunction/'$function'/' ./"${function,,}"_test.go
sed -i -e 's/mypackagebeingfuzzed/'$fuzzed_package'/' ./"${function,,}"_test.go
sed -i -e 's/TestFuzzCorpus/Test'$function'Corpus/' ./"${function,,}"_test.go
# The repo is the module path/name, which is already created above
# in case it doesn't exist, but not always the same as the module
# path. This is necessary to handle SIV properly.
fuzzed_repo=$(go list $tags -f {{.Module}} "$path")
abspath_repo=`go list -m $tags -f {{.Dir}} $fuzzed_repo || go list $tags -f {{.Dir}} $fuzzed_repo`
# give equivalence to absolute paths in another file, as go test -cover uses golangish pkg.Dir
echo "s=$fuzzed_repo"="$abspath_repo"= > $OUT/$fuzzer.gocovpath
go test -run Test${function}Corpus -v $tags -coverpkg $fuzzed_repo/... -c -o $OUT/$fuzzer $path
rm ./"${function,,}"_test.go
else
go-118-fuzz-build -o $fuzzer.a -func $function $abs_file_dir
$CXX $CXXFLAGS $LIB_FUZZING_ENGINE $fuzzer.a -o $OUT/$fuzzer \
$ADDITIONAL_LIBS
fi
}
path=$1
function=$2
fuzzer=$3
tags="-tags gofuzz"
# Get absolute path.
abs_file_dir=$(go list $tags -f {{.Dir}} $path)
# TODO(adamkorcz): Get rid of "-r" flag here.
fuzzer_filename=$(grep -r -l --include='*.go' -s "$function" "${abs_file_dir}")
# Test if file contains a line with "func $function" and "testing.F".
if [ $(grep -r "func $function" $fuzzer_filename | grep "testing.F" | wc -l) -eq 1 ]
then
rewrite_go_fuzz_harness $fuzzer_filename $function
build_native_go_fuzzer $fuzzer $function $abs_file_dir
# Clean up.
rm "${fuzzer_filename}_fuzz_.go"
mv /tmp/$(basename $fuzzer_filename) $fuzzer_filename
else
echo "Could not find the function: func ${function}(f *testing.F)"
fi

133
tests/fuzz/oss_fuzz_build.sh Executable file
View File

@ -0,0 +1,133 @@
#!/usr/bin/env bash
# Copyright 2022 The Flux authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -euxo pipefail
LIBGIT2_TAG="${LIBGIT2_TAG:-v0.4.0}"
GOPATH="${GOPATH:-/root/go}"
GO_SRC="${GOPATH}/src"
PROJECT_PATH="github.com/fluxcd/image-automation-controller"
TMP_DIR=$(mktemp -d /tmp/oss_fuzz-XXXXXX)
cleanup(){
rm -rf "${TMP_DIR}"
}
trap cleanup EXIT
install_deps(){
if ! command -v go-118-fuzz-build &> /dev/null || ! command -v addimport &> /dev/null; then
mkdir -p "${TMP_DIR}/go-118-fuzz-build"
git clone https://github.com/AdamKorcz/go-118-fuzz-build "${TMP_DIR}/go-118-fuzz-build"
cd "${TMP_DIR}/go-118-fuzz-build"
go build -o "${GOPATH}/bin/go-118-fuzz-build"
cd addimport
go build -o "${GOPATH}/bin/addimport"
fi
if ! command -v goimports &> /dev/null; then
go install golang.org/x/tools/cmd/goimports@latest
fi
}
# Removes the content of test funcs which could cause the Fuzz
# tests to break.
remove_test_funcs(){
filename=$1
echo "removing co-located *testing.T"
sed -i -e '/func Test.*testing.T) {$/ {:r;/\n}/!{N;br}; s/\n.*\n/\n/}' "${filename}"
# Remove gomega reference as it is not used by Fuzz tests.
sed -i 's;. "github.com/onsi/gomega";;g' "${filename}"
# After removing the body of the go testing funcs, consolidate the imports.
goimports -w "${filename}"
}
install_deps
cd "${GO_SRC}/${PROJECT_PATH}"
export TARGET_DIR="$(/bin/pwd)/build/libgit2/${LIBGIT2_TAG}"
# For most cases, libgit2 will already be present.
# The exception being at the oss-fuzz integration.
if [ ! -d "${TARGET_DIR}" ]; then
curl -o output.tar.gz -LO "https://github.com/fluxcd/golang-with-libgit2/releases/download/${LIBGIT2_TAG}/linux-$(uname -m)-libgit2-only.tar.gz"
DIR=linux-libgit2-only
NEW_DIR="$(/bin/pwd)/build/libgit2/${LIBGIT2_TAG}"
INSTALLED_DIR="/home/runner/work/golang-with-libgit2/golang-with-libgit2/build/${DIR}"
mkdir -p ./build/libgit2
tar -xf output.tar.gz
rm output.tar.gz
mv "${DIR}" "${LIBGIT2_TAG}"
mv "${LIBGIT2_TAG}/" "./build/libgit2"
# Update the prefix paths included in the .pc files.
# This will make it easier to update to the location in which they will be used.
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "s;${INSTALLED_DIR};${NEW_DIR};g" {}
fi
go get github.com/AdamKorcz/go-118-fuzz-build/utils
# Setup files to be embedded into controllers_fuzzer.go's testFiles variable.
mkdir -p controllers/testdata/crd
cp config/crd/bases/*.yaml controllers/testdata/crd
export CGO_ENABLED=1
export LIBRARY_PATH="${TARGET_DIR}/lib"
export PKG_CONFIG_PATH="${TARGET_DIR}/lib/pkgconfig"
export CGO_CFLAGS="-I${TARGET_DIR}/include"
export CGO_LDFLAGS="$(pkg-config --libs --static --cflags libgit2)"
# Version of the source-controller from which to get the GitRepository CRD.
# Pulls source-controller/api's version set in go.mod.
SOURCE_VER=$(go list -m github.com/fluxcd/source-controller/api | awk '{print $2}')
# Version of the image-reflector-controller from which to get the ImagePolicy CRD.
# Pulls image-reflector-controller/api's version set in go.mod.
REFLECTOR_VER=$(go list -m github.com/fluxcd/image-reflector-controller/api | awk '{print $2}')
if [ -d "../../controllers/testdata/crds" ]; then
cp ../../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 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 controllers/testdata/crd/imagepolicies.yaml
fi
export ADDITIONAL_LIBS="${TARGET_DIR}/lib/libgit2.a"
# Iterate through all Go Fuzz targets, compiling each into a fuzzer.
test_files=$(grep -r --include='**_test.go' --files-with-matches 'func Fuzz' .)
for file in ${test_files}
do
remove_test_funcs "${file}"
targets=$(grep -oP 'func \K(Fuzz\w*)' "${file}")
for target_name in ${targets}
do
fuzzer_name=$(echo "${target_name}" | tr '[:upper:]' '[:lower:]')
target_dir=$(dirname "${file}")
echo "Building ${file}.${target_name} into ${fuzzer_name}"
compile_native_go_fuzzer "${target_dir}" "${target_name}" "${fuzzer_name}"
done
done

View File

@ -1,41 +0,0 @@
#!/usr/bin/env bash
# Copyright 2022 The Flux authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -euxo pipefail
# This file is executed by upstream oss-fuzz for any requirements that
# are specific for building this project.
# 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
# Version of the source-controller from which to get the GitRepository CRD.
# Pulls source-controller/api's version set in go.mod.
SOURCE_VER=$(go list -m github.com/fluxcd/source-controller/api | awk '{print $2}')
# Version of the image-reflector-controller from which to get the ImagePolicy CRD.
# Pulls image-reflector-controller/api's version set in go.mod.
REFLECTOR_VER=$(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
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
fi

View File

@ -17,4 +17,4 @@
set -euxo pipefail
# run each fuzzer once to ensure they are working properly
find /out -type f -iname "fuzz*" -exec echo {} -runs=1 \; | bash -e
find /out -type f -name "fuzz*" -exec echo {} -runs=1 \; | bash -e