Compare commits
532 Commits
api/v0.33.
...
main
Author | SHA1 | Date |
---|---|---|
|
c77a9799fb | |
|
fd132e2846 | |
|
124fd9fe8f | |
|
3999c651b1 | |
|
b8e667d558 | |
|
d8366598f0 | |
|
a4fbfa885b | |
|
009d764d7f | |
|
42af3383fc | |
|
17e585c90d | |
|
45e34fd5b3 | |
|
48e677ab26 | |
|
b4ad41bef5 | |
|
b09cceb8b9 | |
|
4f207d5108 | |
|
e5ae4097be | |
|
48b16c2e51 | |
|
806d41081a | |
|
4addc4e215 | |
|
82f08530f8 | |
|
f7387ccbef | |
|
3d434f0fe7 | |
|
6c5ffee6c0 | |
|
07058a1f60 | |
|
498ec11b09 | |
|
91c2f594b6 | |
|
d86ccc497f | |
|
493ce833b0 | |
|
d48392519c | |
|
081f333a3a | |
|
369d9eb107 | |
|
68b9bc7d0d | |
|
8aa8679f5b | |
|
41a0e4d324 | |
|
e9ed331572 | |
|
640aa7a79b | |
|
80cb694b10 | |
|
b1fc97f6c6 | |
|
a77cd6263f | |
|
93534085af | |
|
ba238df57b | |
|
3d295cc9c1 | |
|
68d9744428 | |
|
6ec657048d | |
|
59b50fae29 | |
|
6ca182eade | |
|
3efde8d870 | |
|
148bd07a1f | |
|
9feaa2cdf6 | |
|
19ebeeb56f | |
|
ef9608d41d | |
|
a67596e3cf | |
|
d5746c5a0a | |
|
2d6cf83f55 | |
|
227559c60a | |
|
8414fa2aa0 | |
|
bb3e96f68c | |
|
9a8e210df8 | |
|
3ae89361df | |
|
e4b925aa9c | |
|
1066a57871 | |
|
6dafa5ce9a | |
|
904289de75 | |
|
c8284d90f0 | |
|
4654113ca4 | |
|
e721c2016a | |
|
674490569e | |
|
bd119da4c8 | |
|
aa24c35eb4 | |
|
5e98055e29 | |
|
f6eed853e3 | |
|
03b9190c45 | |
|
8d5084bbea | |
|
02b165fcda | |
|
79136ad34a | |
|
d5c29a3133 | |
|
f03a53d73e | |
|
4bdd80f0d5 | |
|
9f17c8b21d | |
|
4c9cc99c76 | |
|
9e6023dbf5 | |
|
c243beeb50 | |
|
75e708baff | |
|
28f2164eea | |
|
6b7d26bdf1 | |
|
735f515d03 | |
|
9c7037771c | |
|
a7b5389aaa | |
|
d3550173d6 | |
|
fabeff2876 | |
|
d2763e6d74 | |
|
65f2a43c9b | |
|
d02dec2e6c | |
|
6cd33ab88c | |
|
6d59fcebc8 | |
|
6655e25469 | |
|
e3cd0be322 | |
|
9a32e0c10a | |
|
90821a7925 | |
|
3c846ebaa8 | |
|
67e9cf40d9 | |
|
b1d5d38b37 | |
|
d9ba54b8e0 | |
|
aaeaf52925 | |
|
891d82618e | |
|
7c9408fcda | |
|
0b463c27c8 | |
|
12dfef1342 | |
|
db0998a44c | |
|
8fa6c76df5 | |
|
b205745e5c | |
|
ddd4d61874 | |
|
b25e69b1a9 | |
|
870da77897 | |
|
c2d6a59235 | |
|
d9db01d360 | |
|
b88af04335 | |
|
5b945f7424 | |
|
e1a9080d46 | |
|
54b9f5106c | |
|
8160146a76 | |
|
7294a62885 | |
|
6c91870e91 | |
|
1e0220c016 | |
|
c5ace72c36 | |
|
ea43c5ecb6 | |
|
fcc5b78845 | |
|
ce2617afa0 | |
|
5729d1c094 | |
|
45c61bfe1b | |
|
36b5271988 | |
|
23687d65a9 | |
|
7500b46874 | |
|
1259a519c7 | |
|
5d74f31d3e | |
|
644ef7c9f7 | |
|
ae49037eab | |
|
ebeead0e14 | |
|
189ba5894b | |
|
711504944c | |
|
8599ef13b7 | |
|
131bcd76bc | |
|
ee6f36262f | |
|
e8f1f12349 | |
|
632a84bf48 | |
|
d534149ddc | |
|
3ff8476fbb | |
|
443ef35e64 | |
|
de958604e8 | |
|
87ab708624 | |
|
1d01d43a2a | |
|
1f6b3577fc | |
|
2ccad39527 | |
|
4da0650005 | |
|
34557726e9 | |
|
438705a1b8 | |
|
474e4c39d4 | |
|
d2b33d1165 | |
|
a7cebe3622 | |
|
1a7d0fe5f0 | |
|
e16d086808 | |
|
a9af390d2d | |
|
5755a87be2 | |
|
87bff0e6fd | |
|
b24fb414c3 | |
|
7203b194c5 | |
|
646646bd6e | |
|
d56ac02a95 | |
|
e189b0c271 | |
|
a716f8b292 | |
|
e5343d4f7f | |
|
b93308f488 | |
|
14e5969687 | |
|
649188c828 | |
|
c1c5cc14a8 | |
|
168d33c430 | |
|
1aa234649c | |
|
26cdb3659c | |
|
9a59f96178 | |
|
66256a4f84 | |
|
e11935b48f | |
|
e34881d370 | |
|
0f17d9d9f0 | |
|
2940e1bb8f | |
|
176bf9528a | |
|
db92b7b92b | |
|
f5af4aafe1 | |
|
2902dfa072 | |
|
d5e9ad4e79 | |
|
1aa87f8d79 | |
|
e207b2f81c | |
|
2387dd94a5 | |
|
dc1a6154b8 | |
|
c6a6747474 | |
|
3361a1378c | |
|
beba7d90d2 | |
|
3419b2416b | |
|
af1b13de06 | |
|
edb686882a | |
|
9a9ed7b5f9 | |
|
56bfc37cab | |
|
47c19b4f88 | |
|
0ad8d3904b | |
|
7c22c990ad | |
|
4aba4c1d79 | |
|
50ab2644f2 | |
|
c79407da83 | |
|
d8e7395c9a | |
|
27dc1dca10 | |
|
e6a1f2388d | |
|
b8d2ffb8bf | |
|
6d051b2abc | |
|
fe5bb8d350 | |
|
0fb81f08d9 | |
|
425e3daf37 | |
|
ab64fb477d | |
|
121edb604e | |
|
90b9e0abd1 | |
|
164b992277 | |
|
8e6783fd96 | |
|
0863e022a5 | |
|
b22453a781 | |
|
f78b82579f | |
|
008c5c0634 | |
|
ede9104c40 | |
|
30f4c3dbf3 | |
|
242604b5e3 | |
|
fe8e3a2f18 | |
|
8694e4949d | |
|
25730b122d | |
|
52030916a3 | |
|
7a581f31d9 | |
|
cc5c84431d | |
|
fd99f8c989 | |
|
142eb5ca22 | |
|
2826dde7ea | |
|
4875677707 | |
|
cf8e9917c9 | |
|
88206032dc | |
|
f453888f1f | |
|
3e83225a98 | |
|
32e891a564 | |
|
102e2b3a19 | |
|
317d322803 | |
|
9d4afa9e4b | |
|
12afc54ab1 | |
|
08bd89dc1f | |
|
2b714c5193 | |
|
74c14e48ef | |
|
02f66f4198 | |
|
cb9f657c05 | |
|
651751aaac | |
|
f48e791162 | |
|
df1a9ab6e2 | |
|
6a238db493 | |
|
2cc10fc14a | |
|
799d505696 | |
|
c00665d1af | |
|
0c5cc851cc | |
|
e6507779c7 | |
|
6dc5b34312 | |
|
b49999cfc0 | |
|
02fc3223c8 | |
|
e392237e4a | |
|
f86008ff0c | |
|
587a7501fd | |
|
1653230f3d | |
|
aecec57a9c | |
|
1c7b8896b8 | |
|
0c7b287048 | |
|
8fb20fe3ec | |
|
7bc2cbe3c0 | |
|
44912bd43f | |
|
a523f66f22 | |
|
a33bb0eedb | |
|
e093c3ecd3 | |
|
e028cd962f | |
|
ec6d6871f6 | |
|
785b5f7ca4 | |
|
8743ead5dc | |
|
e722b5f800 | |
|
2948f7ad76 | |
|
7c2a8f6951 | |
|
79788ab52e | |
|
96898c99e3 | |
|
bb485c031a | |
|
dfe6927676 | |
|
899272f208 | |
|
af6c529e4c | |
|
1c829502bf | |
|
5fb7af7441 | |
|
ffcb4d1f31 | |
|
d0a24940d6 | |
|
fd4a40d941 | |
|
6f7010771b | |
|
f5a2940cd7 | |
|
edc5cee43d | |
|
d810aa6a46 | |
|
5789c00945 | |
|
83865d39c9 | |
|
5b3b1e5fba | |
|
e0d0885e32 | |
|
862e56c5e6 | |
|
45b79b2320 | |
|
f5daf358a8 | |
|
9dbcffa35e | |
|
9271c9b6d6 | |
|
682c598e30 | |
|
9728b403b2 | |
|
6fc3712761 | |
|
a43a3e9cc9 | |
|
73f6943de7 | |
|
bf3cf4be0a | |
|
1c4db83b08 | |
|
1698305f77 | |
|
4397249c7f | |
|
0b25ac93c6 | |
|
b424607308 | |
|
f1b8d339ca | |
|
415ef43135 | |
|
65b8b1d5c3 | |
|
cd988f19be | |
|
6e093957e1 | |
|
853a7608ce | |
|
ef6226b5d1 | |
|
4325802e06 | |
|
0f2432c520 | |
|
e50916952a | |
|
2b0b732be5 | |
|
59bb5767ce | |
|
45e5f426b2 | |
|
580ac1a41c | |
|
beca50a1d7 | |
|
021a40ea10 | |
|
c90491e3d0 | |
|
c04ed6521f | |
|
783e2ea9eb | |
|
ca4aa24ee1 | |
|
abefa7a2e8 | |
|
690341e3f4 | |
|
316278ad48 | |
|
8e10e572a5 | |
|
db060d62c5 | |
|
056311120e | |
|
2645767c05 | |
|
1439a5cc64 | |
|
1345d4b1f2 | |
|
540b4e080f | |
|
ca55b11d6f | |
|
d5e199b983 | |
|
00553644cd | |
|
6b6b8363a9 | |
|
17a65b2fee | |
|
b054af2399 | |
|
2fdd3f99c0 | |
|
1648a248b6 | |
|
53c503e456 | |
|
c6a4045119 | |
|
90f5bdf7de | |
|
cc42323be5 | |
|
021193eac1 | |
|
700b4d4755 | |
|
25600e8a34 | |
|
6d8b491fc8 | |
|
13413b2670 | |
|
c9d0a39f34 | |
|
05021967b0 | |
|
f88fe3d441 | |
|
24f60b550e | |
|
06a0e025a1 | |
|
bac0f22481 | |
|
057be9488b | |
|
a739af5509 | |
|
ede60037a8 | |
|
387e82c9ac | |
|
e3f74faefb | |
|
e953ae18dc | |
|
5adfb0779b | |
|
0c8056356e | |
|
afc58b6806 | |
|
8bbeec5192 | |
|
f4c3b91b96 | |
|
9baade5ba7 | |
|
85702e7795 | |
|
ef4ab7bf19 | |
|
57208bd390 | |
|
078cc33a9f | |
|
eab57fb9c3 | |
|
3d34d8fcd5 | |
|
7ae8cc7f81 | |
|
97ac75d00e | |
|
2963c1688f | |
|
26451fab3b | |
|
8437e1520d | |
|
b7f461350b | |
|
0b56f09e88 | |
|
02dadfd16d | |
|
db8a2577c7 | |
|
73466b6a5a | |
|
390a972e57 | |
|
5120491073 | |
|
81f33c0259 | |
|
57f8d1ae02 | |
|
7c9ef11254 | |
|
660ad9f3fd | |
|
ed658e6bb1 | |
|
8f451b85cd | |
|
51520ff578 | |
|
d896b4f6ea | |
|
d2f8784180 | |
|
f9bbcf1cae | |
|
644ca35fec | |
|
1dd0e63924 | |
|
1e0fad1feb | |
|
95a7b39de0 | |
|
6f07aad390 | |
|
c120f9134a | |
|
f7c5f690a7 | |
|
192b9e006b | |
|
68ad71b1b3 | |
|
e1273742fd | |
|
491c2cb59d | |
|
a6ac532850 | |
|
46f68d6917 | |
|
6827808a1a | |
|
b66e992faa | |
|
e6570a929a | |
|
e27a9e2482 | |
|
d52002a772 | |
|
4d09a593a3 | |
|
48f2f49d5f | |
|
a7b2de01d2 | |
|
c6e99d3379 | |
|
09aa2aca7a | |
|
eabd20bef8 | |
|
205065a16b | |
|
cea79ca764 | |
|
386d678853 | |
|
23b904bf1d | |
|
5242551eae | |
|
853583395e | |
|
b15b42d547 | |
|
d76e4739b9 | |
|
d0a8fbbb66 | |
|
412c7ea576 | |
|
d29800da24 | |
|
12a05ad6f9 | |
|
901f1b39c5 | |
|
6b1d254e2f | |
|
5e2d4c818c | |
|
8f9bbae903 | |
|
284e2a0ef4 | |
|
886c57a606 | |
|
3dc39ea81c | |
|
51ce64d41e | |
|
9b45bef17a | |
|
1bd7ebf866 | |
|
710afdcca5 | |
|
b00a28c796 | |
|
218cff3953 | |
|
dd780036c9 | |
|
792e43003e | |
|
ad9e52bba6 | |
|
35a621595f | |
|
09da177952 | |
|
bd68550e12 | |
|
a80f1a7f30 | |
|
6456d51cbf | |
|
6524f6e7a1 | |
|
a161319271 | |
|
5f88b30add | |
|
e922335ba0 | |
|
e9b5e2c2b3 | |
|
99590a1e08 | |
|
6d1e442451 | |
|
aca28ae6e4 | |
|
f64fbb947d | |
|
745bfb7043 | |
|
4a04c7c564 | |
|
4e7371c0d6 | |
|
4c397057ee | |
|
a32227f30a | |
|
741227f3e0 | |
|
769ce5e6d3 | |
|
cd568c41b5 | |
|
1d5f7abac4 | |
|
e567c77e4e | |
|
afcc5f58f2 | |
|
fb06ebc63d | |
|
48289ec1ac | |
|
f33fc78bb3 | |
|
3e98646d7c | |
|
7c0529b228 | |
|
faf265e79b | |
|
a85f25d3df | |
|
1d934cc5ee | |
|
9fae8c424c | |
|
a5326a8593 | |
|
3e7cf9b826 | |
|
c656221973 | |
|
8a8ac50087 | |
|
ac427afbfb | |
|
0578f12ea9 | |
|
76a9b2381d | |
|
99009ca9e1 | |
|
bfb2a978ad | |
|
548b612b10 | |
|
45b7c6839f | |
|
43193209cb | |
|
7a0f3eb249 | |
|
ca4a5f3acb | |
|
5c24abe4a5 | |
|
cc59b4b0c5 | |
|
dec38eb800 | |
|
2df56b63ba | |
|
71c1da1f5a | |
|
973f8fb116 | |
|
9174311a21 | |
|
0bac43111e | |
|
2f82afbf55 | |
|
4ac647a5b7 | |
|
7cc9c9c5f4 | |
|
c65a2dfeea | |
|
7e85148a00 | |
|
aeb528929f | |
|
eafea0dfd4 | |
|
18f215617b | |
|
a3fee6d598 | |
|
b193e2d4d9 | |
|
8f84ba5fae | |
|
59501e89e5 | |
|
d359ea7ac7 |
|
@ -0,0 +1,32 @@
|
|||
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"
|
|
@ -0,0 +1,14 @@
|
|||
# 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'
|
|
@ -0,0 +1,31 @@
|
|||
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}.
|
|
@ -2,8 +2,7 @@ name: build
|
|||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [ 'main', 'release/**' ]
|
||||
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
|
@ -13,66 +12,32 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
- name: Restore go cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
# the ff is mounted into the container as ~/go/pkg/mod
|
||||
path: /home/runner/work/_temp/_github_home/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
go-version: 1.24.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Run tests
|
||||
run: make test
|
||||
- name: Verify
|
||||
run: make verify
|
||||
|
||||
kind-linux-arm64:
|
||||
# Hosted on Equinix
|
||||
# Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners
|
||||
runs-on: [self-hosted, Linux, ARM64, equinix]
|
||||
runs-on:
|
||||
group: "ARM64"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
- name: Run tests
|
||||
run: make test
|
||||
env:
|
||||
# Temporarily disabling -race for arm64 as our GitHub action
|
||||
# runners don't seem to like it.
|
||||
#
|
||||
# We should reenable go test -race for arm64 runners once the
|
||||
# current issue is resolved.
|
||||
GO_TEST_ARGS: ""
|
||||
SKIP_COSIGN_VERIFICATION: true
|
||||
- name: Verify
|
||||
run: make verify
|
||||
|
||||
# Runs 'make test' on macos-11 to assure development environment for
|
||||
# contributors using MacOS.
|
||||
darwin-amd64:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
- name: Restore Go cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /home/runner/work/_temp/_github_home/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
go-version: 1.24.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Run tests
|
||||
run: make test
|
||||
env:
|
||||
|
|
|
@ -1,30 +1,23 @@
|
|||
name: CIFuzz
|
||||
name: fuzz
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
branches: [ 'main', 'release/**' ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read # for actions/checkout to fetch code
|
||||
|
||||
jobs:
|
||||
Fuzzing:
|
||||
smoketest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
- name: Restore Go cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /home/runner/work/_temp/_github_home/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Smoke test Fuzzers
|
||||
run: make fuzz-smoketest
|
||||
env:
|
||||
SKIP_COSIGN_VERIFICATION: true
|
||||
- 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
|
||||
|
|
|
@ -14,16 +14,15 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
- name: Setup Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
buildkitd-flags: "--debug"
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
- name: Build multi-arch container image
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
|
||||
with:
|
||||
push: false
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
|
|
|
@ -7,22 +7,29 @@ on:
|
|||
inputs:
|
||||
tag:
|
||||
description: 'image tag prefix'
|
||||
default: 'rc'
|
||||
default: 'preview'
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write # needed to write releases
|
||||
id-token: write # needed for keyless signing
|
||||
packages: write # needed for ghcr access
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CONTROLLER: ${{ github.event.repository.name }}
|
||||
|
||||
jobs:
|
||||
build-push:
|
||||
release:
|
||||
outputs:
|
||||
hashes: ${{ steps.slsa.outputs.hashes }}
|
||||
image_url: ${{ steps.slsa.outputs.image_url }}
|
||||
image_digest: ${{ steps.slsa.outputs.image_digest }}
|
||||
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:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@main
|
||||
- name: Prepare
|
||||
|
@ -35,24 +42,24 @@ jobs:
|
|||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
- name: Setup Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||
- name: Generate images meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
with:
|
||||
images: |
|
||||
fluxcd/${{ env.CONTROLLER }}
|
||||
|
@ -60,7 +67,8 @@ jobs:
|
|||
tags: |
|
||||
type=raw,value=${{ steps.prep.outputs.VERSION }}
|
||||
- name: Publish images
|
||||
uses: docker/build-push-action@v4
|
||||
id: build-push
|
||||
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
|
||||
with:
|
||||
sbom: true
|
||||
provenance: true
|
||||
|
@ -71,32 +79,82 @@ jobs:
|
|||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- name: Check images
|
||||
run: |
|
||||
docker buildx imagetools inspect docker.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
docker buildx imagetools inspect ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
docker pull docker.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
docker pull ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
- uses: sigstore/cosign-installer@v3
|
||||
- uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
|
||||
- name: Sign images
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: 1
|
||||
run: |
|
||||
cosign sign --yes fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
cosign sign --yes ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
cosign sign --yes fluxcd/${{ env.CONTROLLER }}@${{ steps.build-push.outputs.digest }}
|
||||
cosign sign --yes ghcr.io/fluxcd/${{ env.CONTROLLER }}@${{ steps.build-push.outputs.digest }}
|
||||
- name: Generate release artifacts
|
||||
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
|
||||
echo '[CHANGELOG](https://github.com/fluxcd/${{ env.CONTROLLER }}/blob/main/CHANGELOG.md)' > ./config/release/notes.md
|
||||
- uses: anchore/sbom-action/download-syft@v0
|
||||
- uses: anchore/sbom-action/download-syft@e11c554f704a0b820cbf8c51673f6945e0731532 # v0.20.0
|
||||
- name: Create release and SBOM
|
||||
id: run-goreleaser
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
|
||||
with:
|
||||
version: latest
|
||||
args: release --release-notes=config/release/notes.md --rm-dist --skip-validate
|
||||
args: release --clean --skip=validate
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Generate SLSA metadata
|
||||
id: slsa
|
||||
env:
|
||||
ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
|
||||
run: |
|
||||
hashes=$(echo -E $ARTIFACTS | jq --raw-output '.[] | {name, "digest": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join(" ") | sub("^sha256:";"")' | base64 -w0)
|
||||
echo "hashes=$hashes" >> $GITHUB_OUTPUT
|
||||
|
||||
image_url=fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.version }}
|
||||
echo "image_url=$image_url" >> $GITHUB_OUTPUT
|
||||
|
||||
image_digest=${{ steps.build-push.outputs.digest }}
|
||||
echo "image_digest=$image_digest" >> $GITHUB_OUTPUT
|
||||
|
||||
release-provenance:
|
||||
needs: [release]
|
||||
permissions:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
contents: write # for uploading attestations to GitHub releases.
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
with:
|
||||
provenance-name: "provenance.intoto.jsonl"
|
||||
base64-subjects: "${{ needs.release.outputs.hashes }}"
|
||||
upload-assets: true
|
||||
|
||||
dockerhub-provenance:
|
||||
needs: [release]
|
||||
permissions:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for uploading attestations.
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: ${{ needs.release.outputs.image_url }}
|
||||
digest: ${{ needs.release.outputs.image_digest }}
|
||||
registry-username: fluxcdbot
|
||||
secrets:
|
||||
registry-password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||
|
||||
ghcr-provenance:
|
||||
needs: [release]
|
||||
permissions:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for uploading attestations.
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: ghcr.io/${{ needs.release.outputs.image_url }}
|
||||
digest: ${{ needs.release.outputs.image_digest }}
|
||||
registry-username: fluxcdbot
|
||||
secrets:
|
||||
registry-password: ${{ secrets.GHCR_TOKEN }}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
name: Scan
|
||||
name: scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [ 'main', 'release/**' ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [ 'main', 'release/**' ]
|
||||
schedule:
|
||||
- cron: '18 10 * * 3'
|
||||
|
||||
|
@ -17,9 +17,10 @@ jobs:
|
|||
name: FOSSA
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Run FOSSA scan and upload build data
|
||||
uses: fossa-contrib/fossa-action@v2
|
||||
uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1
|
||||
with:
|
||||
# FOSSA Push-Only API Token
|
||||
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
||||
|
@ -29,17 +30,23 @@ jobs:
|
|||
name: CodeQL
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
go-version: 1.24.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||
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@v2
|
||||
uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
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
|
|
@ -2,7 +2,7 @@ notes
|
|||
|
||||
# Thes are downloaded in the Makefile
|
||||
cache/*
|
||||
internal/controllers/testdata/crds/*
|
||||
internal/controller/testdata/crds/*
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
|
@ -33,4 +33,4 @@ testbin
|
|||
build/
|
||||
|
||||
# CRDs for fuzzing tests.
|
||||
internal/controllers/testdata/crd
|
||||
internal/controller/testdata/crd
|
||||
|
|
|
@ -4,9 +4,26 @@ 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:
|
||||
|
|
532
CHANGELOG.md
532
CHANGELOG.md
|
@ -1,5 +1,537 @@
|
|||
# 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
|
||||
|
|
|
@ -24,7 +24,7 @@ If any of the above dependencies are not present on your system, the first invoc
|
|||
## How to run the test suite
|
||||
|
||||
Prerequisites:
|
||||
* Go >= 1.18
|
||||
* Go >= 1.24
|
||||
|
||||
You can run the test suite by simply doing
|
||||
|
||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -1,10 +1,10 @@
|
|||
ARG BASE_VARIANT=alpine
|
||||
ARG GO_VERSION=1.20
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG GO_VERSION=1.24
|
||||
ARG XX_VERSION=1.6.1
|
||||
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${BASE_VARIANT} as gostable
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${BASE_VARIANT} AS gostable
|
||||
|
||||
FROM gostable AS go-linux
|
||||
|
||||
|
@ -17,7 +17,7 @@ RUN apk add clang lld
|
|||
COPY --from=xx / /
|
||||
|
||||
# build can still be cached at build platform architecture.
|
||||
FROM build-base as build
|
||||
FROM build-base AS build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
|
@ -60,7 +60,7 @@ RUN export CGO_LDFLAGS="-static -fuse-ld=lld" && \
|
|||
# Ensure that the binary was cross-compiled correctly to the target platform.
|
||||
RUN xx-verify --static /image-automation-controller
|
||||
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.21
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN apk --no-cache add ca-certificates \
|
||||
|
|
|
@ -9,6 +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)
|
||||
|
||||
Retired maintainers:
|
||||
|
|
11
Makefile
11
Makefile
|
@ -46,7 +46,7 @@ ifeq ($(shell uname -s),Linux)
|
|||
endif
|
||||
|
||||
# API (doc) generation utilities
|
||||
CONTROLLER_GEN_VERSION ?= v0.11.1
|
||||
CONTROLLER_GEN_VERSION ?= v0.16.1
|
||||
GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113
|
||||
|
||||
# If gobin not set, create one on ./build and add to path.
|
||||
|
@ -69,7 +69,7 @@ ifeq ($(shell uname -s),Darwin)
|
|||
ENVTEST_ARCH=amd64
|
||||
endif
|
||||
|
||||
TEST_CRDS := internal/controllers/testdata/crds
|
||||
TEST_CRDS := internal/controller/testdata/crds
|
||||
|
||||
# Log level for `make run`
|
||||
LOG_LEVEL ?= info
|
||||
|
@ -139,14 +139,15 @@ 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/v1beta1 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/image-automation.md
|
||||
$(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
|
||||
|
||||
tidy: ## Run go mod tidy
|
||||
cd api; rm -f go.sum; go mod tidy -compat=1.20
|
||||
rm -f go.sum; go mod tidy -compat=1.20
|
||||
cd api; rm -f go.sum; go mod tidy -compat=1.24
|
||||
rm -f go.sum; go mod tidy -compat=1.24
|
||||
|
||||
fmt: ## Run go fmt against code
|
||||
go fmt ./...
|
||||
|
|
3
PROJECT
3
PROJECT
|
@ -4,4 +4,7 @@ resources:
|
|||
- group: image
|
||||
kind: ImageUpdateAutomation
|
||||
version: v1beta1
|
||||
- group: image
|
||||
kind: ImageUpdateAutomation
|
||||
version: v1beta2
|
||||
version: "2"
|
||||
|
|
33
api/go.mod
33
api/go.mod
|
@ -1,27 +1,32 @@
|
|||
module github.com/fluxcd/image-automation-controller/api
|
||||
|
||||
go 1.18
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/fluxcd/pkg/apis/meta v1.0.0
|
||||
github.com/fluxcd/source-controller/api v1.0.0-rc.2
|
||||
k8s.io/apimachinery v0.26.3
|
||||
sigs.k8s.io/controller-runtime v0.14.6
|
||||
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
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
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/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
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/text v0.7.0 // 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
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/klog/v2 v2.80.1 // indirect
|
||||
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
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
|
||||
)
|
||||
|
|
104
api/go.sum
104
api/go.sum
|
@ -1,38 +1,57 @@
|
|||
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/meta v1.0.0 h1:i9IGHd/VNEZELX7mepkiYFbJxs2J5znaB4cN9z2nPm8=
|
||||
github.com/fluxcd/pkg/apis/meta v1.0.0/go.mod h1:04ZdpZYm1x+aL93K4daNHW1UX6E8K7Gyf5za9OhrE+U=
|
||||
github.com/fluxcd/source-controller/api v1.0.0-rc.2 h1:14S47wfNrN92KqLkpbfSgWr84ALhogbFgaRapit/9XI=
|
||||
github.com/fluxcd/source-controller/api v1.0.0-rc.2/go.mod h1:CvGNdS8g/MqwpERUK6aJp4lndsrm+JBzGpoyyZ4u0c8=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/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/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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc=
|
||||
github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
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/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.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
@ -44,46 +63,53 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
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/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
|
||||
k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k=
|
||||
k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
|
||||
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
|
||||
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y=
|
||||
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA=
|
||||
sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
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=
|
||||
|
|
|
@ -85,6 +85,20 @@ type PushSpec struct {
|
|||
// Branch specifies that commits should be pushed to the branch
|
||||
// named. The branch is created using `.spec.checkout.branch` as the
|
||||
// starting point, if it doesn't already exist.
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
// +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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
Copyright 2024 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -105,7 +104,7 @@ func (in *GitSpec) DeepCopyInto(out *GitSpec) {
|
|||
if in.Push != nil {
|
||||
in, out := &in.Push, &out.Push
|
||||
*out = new(PushSpec)
|
||||
**out = **in
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,6 +238,13 @@ 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.
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
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"
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
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
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
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"`
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
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
|
||||
)
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
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{})
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
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
|
||||
}
|
|
@ -0,0 +1,344 @@
|
|||
//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
|
||||
}
|
|
@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.1
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
name: imageupdateautomations.image.toolkit.fluxcd.io
|
||||
spec:
|
||||
group: image.toolkit.fluxcd.io
|
||||
|
@ -19,6 +18,8 @@ spec:
|
|||
- jsonPath: .status.lastAutomationRunTime
|
||||
name: Last run
|
||||
type: string
|
||||
deprecated: true
|
||||
deprecationWarning: v1beta1 ImageUpdateAutomation is deprecated, upgrade to v1beta2
|
||||
name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
|
@ -26,14 +27,19 @@ 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
|
||||
|
@ -41,36 +47,39 @@ spec:
|
|||
description: ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation
|
||||
properties:
|
||||
git:
|
||||
description: GitSpec contains all the git-specific definitions. This
|
||||
is technically optional, but in practice mandatory until there are
|
||||
description: |-
|
||||
GitSpec contains all the git-specific definitions. This is
|
||||
technically optional, but in practice mandatory until there are
|
||||
other kinds of source allowed.
|
||||
properties:
|
||||
checkout:
|
||||
description: Checkout gives the parameters for cloning the git
|
||||
repository, ready to make changes. If not present, the `spec.ref`
|
||||
field from the referenced `GitRepository` or its default will
|
||||
be used.
|
||||
description: |-
|
||||
Checkout gives the parameters for cloning the git repository,
|
||||
ready to make changes. If not present, the `spec.ref` field from the
|
||||
referenced `GitRepository` or its default will be used.
|
||||
properties:
|
||||
ref:
|
||||
description: Reference gives a branch, tag or commit to clone
|
||||
from the Git repository.
|
||||
description: |-
|
||||
Reference gives a branch, tag or commit to clone from the Git
|
||||
repository.
|
||||
properties:
|
||||
branch:
|
||||
description: Branch to check out, defaults to 'master'
|
||||
if no other field is defined.
|
||||
type: string
|
||||
commit:
|
||||
description: "Commit SHA to check out, takes precedence
|
||||
over all reference fields. \n This can be combined with
|
||||
Branch to shallow clone the branch, in which the commit
|
||||
is expected to exist."
|
||||
description: |-
|
||||
Commit SHA to check out, takes precedence over all reference fields.
|
||||
|
||||
This can be combined with Branch to shallow clone the branch, in which
|
||||
the commit is expected to exist.
|
||||
type: string
|
||||
name:
|
||||
description: "Name of the reference to check out; takes
|
||||
precedence over Branch, Tag and SemVer. \n It must be
|
||||
a valid Git reference: https://git-scm.com/docs/git-check-ref-format#_description
|
||||
Examples: \"refs/heads/main\", \"refs/tags/v0.1.0\",
|
||||
\"refs/pull/420/head\", \"refs/merge-requests/1/head\""
|
||||
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
|
||||
|
@ -87,8 +96,9 @@ 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
|
||||
|
@ -102,19 +112,20 @@ 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.
|
||||
|
@ -122,33 +133,53 @@ 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.
|
||||
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
|
||||
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:
|
||||
|
@ -172,20 +203,23 @@ 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
|
||||
|
@ -208,43 +242,35 @@ spec:
|
|||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
use as an array at the field path .status.conditions. For example,
|
||||
\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 }"
|
||||
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.
|
||||
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
|
||||
|
@ -259,10 +285,6 @@ 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
|
||||
|
@ -275,19 +297,22 @@ 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.
|
||||
|
@ -299,6 +324,399 @@ 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: {}
|
||||
|
|
|
@ -5,4 +5,4 @@ resources:
|
|||
images:
|
||||
- name: fluxcd/image-automation-controller
|
||||
newName: fluxcd/image-automation-controller
|
||||
newTag: v0.33.0
|
||||
newTag: v0.41.1
|
||||
|
|
|
@ -1,11 +1,38 @@
|
|||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: manager-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- image.toolkit.fluxcd.io
|
||||
resources:
|
||||
- imagepolicies
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- image.toolkit.fluxcd.io
|
||||
resources:
|
||||
- imagepolicies/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- image.toolkit.fluxcd.io
|
||||
resources:
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
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
|
|
@ -1,4 +1,4 @@
|
|||
<h1>Image update automation API reference</h1>
|
||||
<h1>Image update automation API reference v1beta1</h1>
|
||||
<p>Packages:</p>
|
||||
<ul class="simple">
|
||||
<li>
|
||||
|
@ -638,11 +638,42 @@ string
|
|||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Branch specifies that commits should be pushed to the branch
|
||||
named. The branch is created using <code>.spec.checkout.branch</code> as the
|
||||
starting point, if it doesn’t already exist.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>refspec</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Refspec specifies the Git Refspec to use for a push operation.
|
||||
If both Branch and Refspec are provided, then the commit is pushed
|
||||
to the branch and also using the specified refspec.
|
||||
For more details about Git Refspecs, see:
|
||||
<a href="https://git-scm.com/book/en/v2/Git-Internals-The-Refspec">https://git-scm.com/book/en/v2/Git-Internals-The-Refspec</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>options</code><br>
|
||||
<em>
|
||||
map[string]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Options specifies the push options that are sent to the Git
|
||||
server when performing a push operation. For details, see:
|
||||
<a href="https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt">https://git-scm.com/docs/git-push#Documentation/git-push.txt—push-optionltoptiongt</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -0,0 +1,907 @@
|
|||
<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’s name.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>tag</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Tag is the image’s tag.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>digest</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Digest is the image’s digest.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomation">ImageUpdateAutomation
|
||||
</h3>
|
||||
<p>ImageUpdateAutomation is the Schema for the imageupdateautomations API</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>metadata</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta">
|
||||
Kubernetes meta/v1.ObjectMeta
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
Refer to the Kubernetes API documentation for the fields of the
|
||||
<code>metadata</code> field.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>spec</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">
|
||||
ImageUpdateAutomationSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<br/>
|
||||
<br/>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<code>sourceRef</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CrossNamespaceSourceReference">
|
||||
CrossNamespaceSourceReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>SourceRef refers to the resource giving access details
|
||||
to a git repository.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>git</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">
|
||||
GitSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>GitSpec contains all the git-specific definitions. This is
|
||||
technically optional, but in practice mandatory until there are
|
||||
other kinds of source allowed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>interval</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
Kubernetes meta/v1.Duration
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Interval gives an lower bound for how often the automation
|
||||
run should be attempted.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>policySelector</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselector-v1-meta">
|
||||
Kubernetes meta/v1.LabelSelector
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>PolicySelector allows to filter applied policies based on labels.
|
||||
By default includes all policies in namespace.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>update</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">
|
||||
UpdateStrategy
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Update gives the specification for how to update the files in
|
||||
the repository. This can be left empty, to use the default
|
||||
value.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>suspend</code><br>
|
||||
<em>
|
||||
bool
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Suspend tells the controller to not run this automation, until
|
||||
it is unset (or set to false). Defaults to false.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>status</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationStatus">
|
||||
ImageUpdateAutomationStatus
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomation">ImageUpdateAutomation</a>)
|
||||
</p>
|
||||
<p>ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>sourceRef</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CrossNamespaceSourceReference">
|
||||
CrossNamespaceSourceReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>SourceRef refers to the resource giving access details
|
||||
to a git repository.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>git</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">
|
||||
GitSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>GitSpec contains all the git-specific definitions. This is
|
||||
technically optional, but in practice mandatory until there are
|
||||
other kinds of source allowed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>interval</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
Kubernetes meta/v1.Duration
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Interval gives an lower bound for how often the automation
|
||||
run should be attempted.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>policySelector</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselector-v1-meta">
|
||||
Kubernetes meta/v1.LabelSelector
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>PolicySelector allows to filter applied policies based on labels.
|
||||
By default includes all policies in namespace.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>update</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">
|
||||
UpdateStrategy
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Update gives the specification for how to update the files in
|
||||
the repository. This can be left empty, to use the default
|
||||
value.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>suspend</code><br>
|
||||
<em>
|
||||
bool
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Suspend tells the controller to not run this automation, until
|
||||
it is unset (or set to false). Defaults to false.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationStatus">ImageUpdateAutomationStatus
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomation">ImageUpdateAutomation</a>)
|
||||
</p>
|
||||
<p>ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>lastAutomationRunTime</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#time-v1-meta">
|
||||
Kubernetes meta/v1.Time
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>LastAutomationRunTime records the last time the controller ran
|
||||
this automation through to completion (even if no updates were
|
||||
made).</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>lastPushCommit</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>LastPushCommit records the SHA1 of the last commit made by the
|
||||
controller, for this automation object</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>lastPushTime</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#time-v1-meta">
|
||||
Kubernetes meta/v1.Time
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>LastPushTime records the time of the last pushed change.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedGeneration</code><br>
|
||||
<em>
|
||||
int64
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>conditions</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#condition-v1-meta">
|
||||
[]Kubernetes meta/v1.Condition
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedPolicies</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ObservedPolicies">
|
||||
ObservedPolicies
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ObservedPolicies is the list of observed ImagePolicies that were
|
||||
considered by the ImageUpdateAutomation update process.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedSourceRevision</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ObservedPolicies []ObservedPolicy <code>json:"observedPolicies,omitempty"</code>
|
||||
ObservedSourceRevision is the last observed source revision. This can be
|
||||
used to determine if the source has been updated since last observation.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>ReconcileRequestStatus</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#ReconcileRequestStatus">
|
||||
github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>
|
||||
(Members of <code>ReconcileRequestStatus</code> are embedded into this type.)
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ObservedPolicies">ObservedPolicies
|
||||
(<code>map[string]./api/v1beta2.ImageRef</code> alias)</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationStatus">ImageUpdateAutomationStatus</a>)
|
||||
</p>
|
||||
<p>ObservedPolicies is a map of policy name and ImageRef of their latest
|
||||
ImageRef.</p>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.PushSpec">PushSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">GitSpec</a>)
|
||||
</p>
|
||||
<p>PushSpec specifies how and where to push commits.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>branch</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Branch specifies that commits should be pushed to the branch
|
||||
named. The branch is created using <code>.spec.checkout.branch</code> as the
|
||||
starting point, if it doesn’t already exist.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>refspec</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Refspec specifies the Git Refspec to use for a push operation.
|
||||
If both Branch and Refspec are provided, then the commit is pushed
|
||||
to the branch and also using the specified refspec.
|
||||
For more details about Git Refspecs, see:
|
||||
<a href="https://git-scm.com/book/en/v2/Git-Internals-The-Refspec">https://git-scm.com/book/en/v2/Git-Internals-The-Refspec</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>options</code><br>
|
||||
<em>
|
||||
map[string]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Options specifies the push options that are sent to the Git
|
||||
server when performing a push operation. For details, see:
|
||||
<a href="https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt">https://git-scm.com/docs/git-push#Documentation/git-push.txt—push-optionltoptiongt</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.SigningKey">SigningKey
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CommitSpec">CommitSpec</a>)
|
||||
</p>
|
||||
<p>SigningKey references a Kubernetes secret that contains a GPG keypair</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>secretRef</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>SecretRef holds the name to a secret that contains a ‘git.asc’ key
|
||||
corresponding to the ASCII Armored file containing the GPG signing
|
||||
keypair as the value. It must be in the same namespace as the
|
||||
ImageUpdateAutomation.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">UpdateStrategy
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec</a>)
|
||||
</p>
|
||||
<p>UpdateStrategy is a union of the various strategies for updating
|
||||
the Git repository. Parameters for each strategy (if any) can be
|
||||
inlined here.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>strategy</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategyName">
|
||||
UpdateStrategyName
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Strategy names the strategy to be used.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>path</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Path to the directory containing the manifests to be updated.
|
||||
Defaults to ‘None’, which translates to the root path
|
||||
of the GitRepositoryRef.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.UpdateStrategyName">UpdateStrategyName
|
||||
(<code>string</code> alias)</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">UpdateStrategy</a>)
|
||||
</p>
|
||||
<p>UpdateStrategyName is the type for names that go in
|
||||
.update.strategy. NB the value in the const immediately below.</p>
|
||||
<div class="admonition note">
|
||||
<p class="last">This page was automatically generated with <code>gen-crd-api-reference-docs</code></p>
|
||||
</div>
|
|
@ -1,6 +1,8 @@
|
|||
<!-- -*- 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.
|
||||
|
||||
|
@ -264,7 +266,7 @@ The message template is a [Go text template][go-text-template]. The data availab
|
|||
have this structure (not reproduced verbatim):
|
||||
|
||||
```go
|
||||
// controllers/imageupdateautomation_controller.go
|
||||
// internal/controller/imageupdateautomation_controller.go
|
||||
|
||||
// TemplateData is the type of the value given to the commit message
|
||||
// template.
|
||||
|
@ -396,6 +398,7 @@ 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.
|
||||
|
@ -406,17 +409,34 @@ type PushSpec struct {
|
|||
// Branch specifies that commits should be pushed to the branch
|
||||
// named. The branch is created using `.spec.checkout.branch` as the
|
||||
// starting point, if it doesn't already exist.
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
// +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"`
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
When `push` is present, the `branch` field specifies a branch to push to at the origin. The branch
|
||||
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
|
||||
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`,
|
||||
|
@ -425,6 +445,16 @@ Note that without force push in push branches, if the target branch is stale, th
|
|||
be able to conclude the operation and will consistently fail until the branch is either deleted or
|
||||
refreshed.
|
||||
|
||||
If both `.push.refspec` and `.push.branch` are specified, then the reconciler will perform
|
||||
two push operations, one to the specified branch and another using the specified refspec.
|
||||
This is particularly useful for working with Gerrit servers. For more information about this,
|
||||
please refer to the [Gerrit](#gerrit) section.
|
||||
|
||||
**Note:** If both `.push.refspec` and `.push.branch` are essentially equal to
|
||||
each other (for e.g.: `.push.refspec: refs/heads/main:refs/heads/main` and
|
||||
`.push.branch: main`), then the reconciler might fail to perform the second push
|
||||
operation and error out with an `already up-to-date` error.
|
||||
|
||||
In the following snippet, updates will be pushed as commits to the branch `auto`, and when that
|
||||
branch does not exist at the origin, it will be created locally starting from the branch `main`, and
|
||||
pushed:
|
||||
|
@ -439,6 +469,117 @@ 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
|
||||
|
@ -690,7 +831,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/#specification
|
||||
[git-repo-ref]: https://fluxcd.io/flux/components/source/gitrepositories/#writing-a-gitrepository-spec
|
||||
[durations]: https://godoc.org/time#ParseDuration
|
||||
[source-docs]: https://fluxcd.io/flux/components/source/gitrepositories/#git-implementation
|
||||
[source-docs]: https://fluxcd.io/flux/components/source/api/v1beta2/#source.toolkit.fluxcd.io/v1beta2.GitRepositorySpec
|
||||
[go-text-template]: https://golang.org/pkg/text/template/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
251
go.mod
251
go.mod
|
@ -1,149 +1,180 @@
|
|||
module github.com/fluxcd/image-automation-controller
|
||||
|
||||
go 1.18
|
||||
go 1.24.0
|
||||
|
||||
replace github.com/fluxcd/image-automation-controller/api => ./api
|
||||
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756
|
||||
github.com/cyphar/filepath-securejoin v0.2.3
|
||||
github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4
|
||||
github.com/fluxcd/image-automation-controller/api v0.33.0
|
||||
github.com/fluxcd/image-reflector-controller/api v0.27.1
|
||||
github.com/fluxcd/pkg/apis/acl v0.1.0
|
||||
github.com/fluxcd/pkg/apis/event v0.4.1
|
||||
github.com/fluxcd/pkg/apis/meta v1.0.0
|
||||
github.com/fluxcd/pkg/git v0.11.0
|
||||
github.com/fluxcd/pkg/git/gogit v0.8.1
|
||||
github.com/fluxcd/pkg/gittestserver v0.8.2
|
||||
github.com/fluxcd/pkg/runtime v0.35.0
|
||||
github.com/fluxcd/pkg/ssh v0.7.3
|
||||
github.com/fluxcd/source-controller/api v1.0.0-rc.2
|
||||
github.com/go-git/go-billy/v5 v5.4.1
|
||||
github.com/go-logr/logr v1.2.4
|
||||
github.com/google/go-containerregistry v0.15.1
|
||||
github.com/onsi/gomega v1.27.6
|
||||
github.com/otiai10/copy v1.11.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
k8s.io/api v0.26.3
|
||||
k8s.io/apimachinery v0.26.3
|
||||
k8s.io/client-go v0.26.3
|
||||
k8s.io/kube-openapi v0.0.0-20230327201221-f5883ff37f0c
|
||||
sigs.k8s.io/controller-runtime v0.14.6
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.9
|
||||
// Pin kustomize to v5.6.0
|
||||
replace (
|
||||
sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.19.0
|
||||
sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/ProtonMail/go-crypto v1.2.0
|
||||
github.com/cyphar/filepath-securejoin v0.4.1
|
||||
github.com/fluxcd/image-automation-controller/api v0.41.1
|
||||
github.com/fluxcd/image-reflector-controller/api v0.35.2
|
||||
github.com/fluxcd/pkg/apis/acl v0.7.0
|
||||
github.com/fluxcd/pkg/apis/event v0.17.0
|
||||
github.com/fluxcd/pkg/apis/meta v1.12.0
|
||||
github.com/fluxcd/pkg/auth v0.17.0
|
||||
github.com/fluxcd/pkg/cache v0.9.0
|
||||
github.com/fluxcd/pkg/git v0.32.0
|
||||
github.com/fluxcd/pkg/git/gogit v0.35.0
|
||||
github.com/fluxcd/pkg/gittestserver v0.17.0
|
||||
github.com/fluxcd/pkg/runtime v0.60.0
|
||||
github.com/fluxcd/pkg/ssh v0.19.0
|
||||
github.com/fluxcd/source-controller/api v1.6.1
|
||||
github.com/go-git/go-billy/v5 v5.6.2
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
github.com/go-logr/logr v1.4.2
|
||||
github.com/google/go-containerregistry v0.20.3
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
k8s.io/api v0.33.0
|
||||
k8s.io/apimachinery v0.33.0
|
||||
k8s.io/client-go v0.33.0
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e
|
||||
sigs.k8s.io/controller-runtime v0.21.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/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/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/cloudflare/circl v1.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.0 // indirect
|
||||
github.com/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/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // 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.2.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-logr/zapr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.1 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||
github.com/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/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/gnostic v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/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/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v1.3.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/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/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.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/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.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.0 // 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.14.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||
github.com/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/skeema/knownhosts v1.1.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/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.1.0 // indirect
|
||||
go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/oauth2 v0.7.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.8.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
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
|
||||
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.26.1 // indirect
|
||||
k8s.io/cli-runtime v0.25.4 // indirect
|
||||
k8s.io/component-base v0.26.3 // indirect
|
||||
k8s.io/klog/v2 v2.90.1 // indirect
|
||||
k8s.io/kubectl v0.25.4 // indirect
|
||||
k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 // indirect
|
||||
sigs.k8s.io/cli-utils v0.34.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/api v0.12.1 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
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
|
||||
)
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
{{ define "packages" }}
|
||||
<h1>Image update automation API reference</h1>
|
||||
<h1>Image update automation API reference
|
||||
{{- with (index .packages 0) -}}
|
||||
{{ with (index .GoPackages 0 ) -}}
|
||||
{{ printf " %s" .Name -}}
|
||||
{{ end -}}
|
||||
{{ end }}</h1>
|
||||
|
||||
{{ with .packages}}
|
||||
<p>Packages:</p>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
Copyright 2024 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
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.
|
||||
|
@ -14,22 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
package constants
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
fuzz "github.com/AdaLogics/go-fuzz-headers"
|
||||
const (
|
||||
// SetterShortHand is a shorthand that can be used to mark
|
||||
// setters; instead of
|
||||
// # { "$ref": "#/definitions/
|
||||
SetterShortHand = "$imagepolicy"
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
|
@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -29,16 +29,16 @@ import (
|
|||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
fuzz "github.com/AdaLogics/go-fuzz-headers"
|
||||
"github.com/fluxcd/go-git/v5"
|
||||
gogit "github.com/fluxcd/go-git/v5"
|
||||
"github.com/fluxcd/go-git/v5/config"
|
||||
"github.com/fluxcd/go-git/v5/plumbing"
|
||||
"github.com/fluxcd/go-git/v5/plumbing/object"
|
||||
"github.com/fluxcd/go-git/v5/storage/memory"
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -57,7 +57,7 @@ import (
|
|||
"github.com/fluxcd/pkg/runtime/testenv"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
image_automationv1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
image_automationv1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/pkg/update"
|
||||
)
|
||||
|
||||
|
@ -85,9 +85,8 @@ func Fuzz_ImageUpdateReconciler(f *testing.F) {
|
|||
utilruntime.Must(ensureDependencies(func(m manager.Manager) {
|
||||
utilruntime.Must((&ImageUpdateAutomationReconciler{
|
||||
Client: m.GetClient(),
|
||||
}).SetupWithManager(m, ImageUpdateAutomationReconcilerOptions{
|
||||
MaxConcurrentReconciles: 4,
|
||||
RateLimiter: controller.GetDefaultRateLimiter(),
|
||||
}).SetupWithManager(context.TODO(), m, ImageUpdateAutomationReconcilerOptions{
|
||||
RateLimiter: controller.GetDefaultRateLimiter(),
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
@ -334,7 +333,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 := git.Init(memory.NewStorage(), fs)
|
||||
repo, err := gogit.Init(memory.NewStorage(), fs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -348,7 +347,7 @@ func initGitRepo(gitServer *gittestserver.GitServer, fixture, branch, repository
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = working.Checkout(&git.CheckoutOptions{
|
||||
if err = working.Checkout(&gogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
Create: true,
|
||||
}); err != nil {
|
||||
|
@ -363,7 +362,7 @@ func initGitRepo(gitServer *gittestserver.GitServer, fixture, branch, repository
|
|||
return err
|
||||
}
|
||||
|
||||
return remote.Push(&git.PushOptions{
|
||||
return remote.Push(&gogit.PushOptions{
|
||||
RefSpecs: []config.RefSpec{
|
||||
config.RefSpec(fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)),
|
||||
},
|
|
@ -0,0 +1,642 @@
|
|||
/*
|
||||
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
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
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()
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -26,14 +26,16 @@ import (
|
|||
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
"github.com/fluxcd/pkg/runtime/controller"
|
||||
"github.com/fluxcd/pkg/runtime/testenv"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
|
@ -44,8 +46,9 @@ import (
|
|||
// Gomega.
|
||||
|
||||
var (
|
||||
testEnv *testenv.Environment
|
||||
ctx = ctrl.SetupSignalHandler()
|
||||
k8sClient client.Client
|
||||
testEnv *testenv.Environment
|
||||
ctx = ctrl.SetupSignalHandler()
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -66,17 +69,28 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
func runTestsWithFeatures(m *testing.M, feats map[string]bool) int {
|
||||
testEnv = testenv.New(testenv.WithCRDPath(
|
||||
filepath.Join("..", "..", "config", "crd", "bases"),
|
||||
filepath.Join("testdata", "crds"),
|
||||
))
|
||||
testEnv = testenv.New(
|
||||
testenv.WithCRDPath(
|
||||
filepath.Join("..", "..", "config", "crd", "bases"),
|
||||
filepath.Join("testdata", "crds"),
|
||||
),
|
||||
testenv.WithMaxConcurrentReconciles(2),
|
||||
)
|
||||
|
||||
var err error
|
||||
// Initialize a cacheless client for tests that need the latest objects.
|
||||
k8sClient, err = client.New(testEnv.Config, client.Options{Scheme: scheme.Scheme})
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create k8s client: %v", err))
|
||||
}
|
||||
|
||||
controllerName := "image-automation-controller"
|
||||
if err := (&ImageUpdateAutomationReconciler{
|
||||
Client: testEnv,
|
||||
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
|
||||
features: feats,
|
||||
}).SetupWithManager(testEnv, ImageUpdateAutomationReconcilerOptions{
|
||||
Client: testEnv,
|
||||
EventRecorder: record.NewFakeRecorder(32),
|
||||
features: feats,
|
||||
ControllerName: controllerName,
|
||||
}).SetupWithManager(ctx, testEnv, ImageUpdateAutomationReconcilerOptions{
|
||||
RateLimiter: controller.GetDefaultRateLimiter(),
|
||||
}); err != nil {
|
||||
panic(fmt.Sprintf("failed to start ImageUpdateAutomationReconciler: %v", err))
|
|
@ -1,645 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
kuberecorder "k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
apiacl "github.com/fluxcd/pkg/apis/acl"
|
||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
"github.com/fluxcd/pkg/git/repository"
|
||||
"github.com/fluxcd/pkg/runtime/acl"
|
||||
helper "github.com/fluxcd/pkg/runtime/controller"
|
||||
"github.com/fluxcd/pkg/runtime/logger"
|
||||
"github.com/fluxcd/pkg/runtime/predicates"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
extgogit "github.com/fluxcd/go-git/v5"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
"github.com/fluxcd/image-automation-controller/internal/features"
|
||||
"github.com/fluxcd/image-automation-controller/pkg/update"
|
||||
)
|
||||
|
||||
const (
|
||||
originRemote = "origin"
|
||||
defaultMessageTemplate = `Update from image update automation`
|
||||
repoRefKey = ".spec.gitRepository"
|
||||
signingSecretKey = "git.asc"
|
||||
signingPassphraseKey = "passphrase"
|
||||
)
|
||||
|
||||
// TemplateData is the type of the value given to the commit message
|
||||
// template.
|
||||
type TemplateData struct {
|
||||
AutomationObject types.NamespacedName
|
||||
Updated update.Result
|
||||
}
|
||||
|
||||
// ImageUpdateAutomationReconciler reconciles a ImageUpdateAutomation object
|
||||
type ImageUpdateAutomationReconciler struct {
|
||||
client.Client
|
||||
EventRecorder kuberecorder.EventRecorder
|
||||
helper.Metrics
|
||||
|
||||
NoCrossNamespaceRef bool
|
||||
|
||||
features map[string]bool
|
||||
}
|
||||
|
||||
type ImageUpdateAutomationReconcilerOptions struct {
|
||||
MaxConcurrentReconciles int
|
||||
RateLimiter ratelimiter.RateLimiter
|
||||
RecoverPanic bool
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imageupdateautomations,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imageupdateautomations/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=gitrepositories,verbs=get;list;watch
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
debuglog := log.V(logger.DebugLevel)
|
||||
tracelog := log.V(logger.TraceLevel)
|
||||
start := time.Now()
|
||||
var templateValues TemplateData
|
||||
|
||||
var auto imagev1.ImageUpdateAutomation
|
||||
if err := r.Get(ctx, req.NamespacedName, &auto); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
// Add our finalizer if it does not exist.
|
||||
if !controllerutil.ContainsFinalizer(&auto, imagev1.ImageUpdateAutomationFinalizer) {
|
||||
patch := client.MergeFrom(auto.DeepCopy())
|
||||
controllerutil.AddFinalizer(&auto, imagev1.ImageUpdateAutomationFinalizer)
|
||||
if err := r.Patch(ctx, &auto, patch); err != nil {
|
||||
log.Error(err, "unable to register finalizer")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// If the object is under deletion, record the readiness, and remove our finalizer.
|
||||
if !auto.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
controllerutil.RemoveFinalizer(&auto, imagev1.ImageUpdateAutomationFinalizer)
|
||||
if err := r.Update(ctx, &auto); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// record suspension metrics
|
||||
r.RecordSuspend(ctx, &auto, auto.Spec.Suspend)
|
||||
|
||||
if auto.Spec.Suspend {
|
||||
log.Info("ImageUpdateAutomation is suspended, skipping automation run")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
templateValues.AutomationObject = req.NamespacedName
|
||||
|
||||
defer func() {
|
||||
// Always record readiness and duration metrics
|
||||
r.Metrics.RecordReadiness(ctx, &auto)
|
||||
r.Metrics.RecordDuration(ctx, &auto, start)
|
||||
}()
|
||||
|
||||
// whatever else happens, we've now "seen" the reconcile
|
||||
// annotation if it's there
|
||||
if token, ok := meta.ReconcileAnnotationValue(auto.GetAnnotations()); ok {
|
||||
auto.Status.SetLastHandledReconcileRequest(token)
|
||||
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
}
|
||||
|
||||
// failWithError is a helper for bailing on the reconciliation.
|
||||
failWithError := func(err error) (ctrl.Result, error) {
|
||||
r.event(ctx, auto, eventv1.EventSeverityError, err.Error())
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.ReconciliationFailedReason, err.Error())
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
log.Error(err, "failed to reconcile")
|
||||
}
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
|
||||
// get the git repository object so it can be checked out
|
||||
|
||||
// only GitRepository objects are supported for now
|
||||
if kind := auto.Spec.SourceRef.Kind; kind != sourcev1.GitRepositoryKind {
|
||||
return failWithError(fmt.Errorf("source kind '%s' not supported", kind))
|
||||
}
|
||||
|
||||
gitSpec := auto.Spec.GitSpec
|
||||
if gitSpec == nil {
|
||||
return failWithError(fmt.Errorf("source kind %s neccessitates field .spec.git", sourcev1.GitRepositoryKind))
|
||||
}
|
||||
|
||||
var origin sourcev1.GitRepository
|
||||
gitRepoNamespace := req.Namespace
|
||||
if auto.Spec.SourceRef.Namespace != "" {
|
||||
gitRepoNamespace = auto.Spec.SourceRef.Namespace
|
||||
}
|
||||
originName := types.NamespacedName{
|
||||
Name: auto.Spec.SourceRef.Name,
|
||||
Namespace: gitRepoNamespace,
|
||||
}
|
||||
debuglog.Info("fetching git repository", "gitrepository", originName)
|
||||
|
||||
if r.NoCrossNamespaceRef && gitRepoNamespace != auto.GetNamespace() {
|
||||
err := acl.AccessDeniedError(fmt.Sprintf("can't access '%s/%s', cross-namespace references have been blocked",
|
||||
auto.Spec.SourceRef.Kind, originName))
|
||||
log.Error(err, "access denied to cross-namespaced resource")
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, apiacl.AccessDeniedReason,
|
||||
err.Error())
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
r.event(ctx, auto, eventv1.EventSeverityError, err.Error())
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
if err := r.Get(ctx, originName, &origin); err != nil {
|
||||
if client.IgnoreNotFound(err) == nil {
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.GitNotAvailableReason, "referenced git repository is missing")
|
||||
log.Error(err, fmt.Sprintf("referenced git repository %s does not exist.", originName.String()))
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
return ctrl.Result{}, nil // and assume we'll hear about it when it arrives
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// validate the git spec and default any values needed later, before proceeding
|
||||
var ref *sourcev1.GitRepositoryRef
|
||||
if gitSpec.Checkout != nil {
|
||||
ref = &gitSpec.Checkout.Reference
|
||||
tracelog.Info("using git repository ref from .spec.git.checkout", "ref", ref)
|
||||
} else if r := origin.Spec.Reference; r != nil {
|
||||
ref = r
|
||||
tracelog.Info("using git repository ref from GitRepository spec", "ref", ref)
|
||||
} // else remain as `nil` and git.DefaultBranch will be used.
|
||||
|
||||
var pushBranch string
|
||||
if gitSpec.Push != nil {
|
||||
pushBranch = gitSpec.Push.Branch
|
||||
tracelog.Info("using push branch from .spec.push.branch", "branch", pushBranch)
|
||||
} else {
|
||||
// Here's where it gets constrained. If there's no push branch
|
||||
// given, then the checkout ref must include a branch, and
|
||||
// that can be used.
|
||||
if ref == nil || ref.Branch == "" {
|
||||
return failWithError(fmt.Errorf("Push branch not given explicitly, and cannot be inferred from .spec.git.checkout.ref or GitRepository .spec.ref"))
|
||||
}
|
||||
pushBranch = ref.Branch
|
||||
tracelog.Info("using push branch from $ref.branch", "branch", pushBranch)
|
||||
}
|
||||
|
||||
tmp, err := os.MkdirTemp("", fmt.Sprintf("%s-%s", originName.Namespace, originName.Name))
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(tmp); err != nil {
|
||||
log.Error(err, "failed to remove working directory", "path", tmp)
|
||||
}
|
||||
}()
|
||||
|
||||
debuglog.Info("attempting to clone git repository", "gitrepository", originName, "ref", ref, "working", tmp)
|
||||
|
||||
authOpts, err := r.getAuthOpts(ctx, &origin)
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage()}
|
||||
forcePush := r.features[features.GitForcePushBranch]
|
||||
if forcePush && pushBranch != ref.Branch {
|
||||
clientOpts = append(clientOpts, gogit.WithForcePush())
|
||||
}
|
||||
if authOpts.Transport == git.HTTP {
|
||||
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
|
||||
}
|
||||
|
||||
// If the push branch is different from the checkout ref, we need to
|
||||
// have all the references downloaded at clone time, to ensure that
|
||||
// SwitchBranch will have access to the target branch state. fluxcd/flux2#3384
|
||||
//
|
||||
// To always overwrite the push branch, the feature gate
|
||||
// GitAllBranchReferences can be set to false, which will cause
|
||||
// the SwitchBranch operation to ignore the remote branch state.
|
||||
allReferences := r.features[features.GitAllBranchReferences]
|
||||
if pushBranch != ref.Branch {
|
||||
clientOpts = append(clientOpts, gogit.WithSingleBranch(!allReferences))
|
||||
}
|
||||
|
||||
gitClient, err := gogit.NewClient(tmp, authOpts, clientOpts...)
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
defer gitClient.Close()
|
||||
|
||||
opts := repository.CloneOptions{}
|
||||
if ref != nil {
|
||||
opts.Tag = ref.Tag
|
||||
opts.SemVer = ref.SemVer
|
||||
opts.Commit = ref.Commit
|
||||
opts.Branch = ref.Branch
|
||||
}
|
||||
|
||||
if enabled, _ := r.features[features.GitShallowClone]; enabled {
|
||||
opts.ShallowClone = true
|
||||
}
|
||||
|
||||
// Use the git operations timeout for the repo.
|
||||
cloneCtx, cancel := context.WithTimeout(ctx, origin.Spec.Timeout.Duration)
|
||||
defer cancel()
|
||||
if _, err := gitClient.Clone(cloneCtx, origin.Spec.URL, opts); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
// When there's a push spec, the pushed-to branch is where commits
|
||||
// shall be made
|
||||
if gitSpec.Push != nil && !(ref != nil && ref.Branch == pushBranch) {
|
||||
// Use the git operations timeout for the repo.
|
||||
fetchCtx, cancel := context.WithTimeout(ctx, origin.Spec.Timeout.Duration)
|
||||
defer cancel()
|
||||
if err := gitClient.SwitchBranch(fetchCtx, pushBranch); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case auto.Spec.Update != nil && auto.Spec.Update.Strategy == imagev1.UpdateStrategySetters:
|
||||
// For setters we first want to compile a list of _all_ the
|
||||
// policies in the same namespace (maybe in the future this
|
||||
// could be filtered by the automation object).
|
||||
var policies imagev1_reflect.ImagePolicyList
|
||||
if err := r.List(ctx, &policies, &client.ListOptions{Namespace: req.NamespacedName.Namespace}); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
manifestsPath := tmp
|
||||
if auto.Spec.Update.Path != "" {
|
||||
tracelog.Info("adjusting update path according to .spec.update.path", "base", tmp, "spec-path", auto.Spec.Update.Path)
|
||||
p, err := securejoin.SecureJoin(tmp, auto.Spec.Update.Path)
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
manifestsPath = p
|
||||
}
|
||||
|
||||
debuglog.Info("updating with setters according to image policies", "count", len(policies.Items), "manifests-path", manifestsPath)
|
||||
if tracelog.Enabled() {
|
||||
for _, item := range policies.Items {
|
||||
tracelog.Info("found policy", "namespace", item.Namespace, "name", item.Name, "latest-image", item.Status.LatestImage)
|
||||
}
|
||||
}
|
||||
|
||||
result, err := updateAccordingToSetters(ctx, tracelog, manifestsPath, manifestsPath, policies.Items)
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
templateValues.Updated = result
|
||||
|
||||
default:
|
||||
log.Info("no update strategy given in the spec")
|
||||
// no sense rescheduling until this resource changes
|
||||
r.event(ctx, auto, eventv1.EventSeverityInfo, "no known update strategy in spec, failing trivially")
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.NoStrategyReason, "no known update strategy is given for object")
|
||||
return ctrl.Result{}, r.patchStatus(ctx, req, auto.Status)
|
||||
}
|
||||
|
||||
debuglog.Info("ran updates to working dir", "working", tmp)
|
||||
|
||||
var statusMessage string
|
||||
var signingEntity *openpgp.Entity
|
||||
if gitSpec.Commit.SigningKey != nil {
|
||||
if signingEntity, err = r.getSigningEntity(ctx, auto); err != nil {
|
||||
failWithError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// construct the commit message from template and values
|
||||
message, err := templateMsg(gitSpec.Commit.MessageTemplate, &templateValues)
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
var rev string
|
||||
if len(templateValues.Updated.Files) > 0 {
|
||||
// The status message depends on what happens next. Since there's
|
||||
// more than one way to succeed, there's some if..else below, and
|
||||
// early returns only on failure.
|
||||
rev, err = gitClient.Commit(
|
||||
git.Commit{
|
||||
Author: git.Signature{
|
||||
Name: gitSpec.Commit.Author.Name,
|
||||
Email: gitSpec.Commit.Author.Email,
|
||||
When: time.Now(),
|
||||
},
|
||||
Message: message,
|
||||
},
|
||||
repository.WithSigner(signingEntity),
|
||||
)
|
||||
} else {
|
||||
err = extgogit.ErrEmptyCommit
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if !errors.Is(err, git.ErrNoStagedFiles) && !errors.Is(err, extgogit.ErrEmptyCommit) {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
log.Info("no changes made in working directory; no commit")
|
||||
statusMessage = "no updates made"
|
||||
|
||||
if auto.Status.LastPushTime != nil && len(auto.Status.LastPushCommit) >= 7 {
|
||||
statusMessage = fmt.Sprintf("%s; last commit %s at %s", statusMessage, auto.Status.LastPushCommit[:7], auto.Status.LastPushTime.Format(time.RFC3339))
|
||||
}
|
||||
} else {
|
||||
// Use the git operations timeout for the repo.
|
||||
pushCtx, cancel := context.WithTimeout(ctx, origin.Spec.Timeout.Duration)
|
||||
defer cancel()
|
||||
if err := gitClient.Push(pushCtx); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
r.event(ctx, auto, eventv1.EventSeverityInfo, fmt.Sprintf("Committed and pushed change %s to %s\n%s", rev, pushBranch, message))
|
||||
log.Info("pushed commit to origin", "revision", rev, "branch", pushBranch)
|
||||
auto.Status.LastPushCommit = rev
|
||||
auto.Status.LastPushTime = &metav1.Time{Time: start}
|
||||
statusMessage = "committed and pushed " + rev + " to " + pushBranch
|
||||
}
|
||||
|
||||
// Getting to here is a successful run.
|
||||
auto.Status.LastAutomationRunTime = &metav1.Time{Time: start}
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionTrue, imagev1.ReconciliationSucceededReason, statusMessage)
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
|
||||
// We're either in this method because something changed, or this
|
||||
// object got requeued. Either way, once successful, we don't need
|
||||
// to see the object again until Interval has passed, or something
|
||||
// changes again.
|
||||
|
||||
interval := intervalOrDefault(&auto)
|
||||
return ctrl.Result{RequeueAfter: interval}, nil
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) SetupWithManager(mgr ctrl.Manager, opts ImageUpdateAutomationReconcilerOptions) error {
|
||||
ctx := context.Background()
|
||||
// Index the git repository object that each I-U-A refers to
|
||||
if err := mgr.GetFieldIndexer().IndexField(ctx, &imagev1.ImageUpdateAutomation{}, repoRefKey, func(obj client.Object) []string {
|
||||
updater := obj.(*imagev1.ImageUpdateAutomation)
|
||||
ref := updater.Spec.SourceRef
|
||||
return []string{ref.Name}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.features == nil {
|
||||
r.features = features.FeatureGates()
|
||||
}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&imagev1.ImageUpdateAutomation{}, builder.WithPredicates(
|
||||
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}))).
|
||||
Watches(&source.Kind{Type: &sourcev1.GitRepository{}}, handler.EnqueueRequestsFromMapFunc(r.automationsForGitRepo)).
|
||||
Watches(&source.Kind{Type: &imagev1_reflect.ImagePolicy{}}, handler.EnqueueRequestsFromMapFunc(r.automationsForImagePolicy)).
|
||||
WithOptions(controller.Options{
|
||||
MaxConcurrentReconciles: opts.MaxConcurrentReconciles,
|
||||
RateLimiter: opts.RateLimiter,
|
||||
RecoverPanic: &opts.RecoverPanic,
|
||||
}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) patchStatus(ctx context.Context,
|
||||
req ctrl.Request,
|
||||
newStatus imagev1.ImageUpdateAutomationStatus) error {
|
||||
|
||||
var auto imagev1.ImageUpdateAutomation
|
||||
if err := r.Get(ctx, req.NamespacedName, &auto); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
patch := client.MergeFrom(auto.DeepCopy())
|
||||
auto.Status = newStatus
|
||||
|
||||
return r.Status().Patch(ctx, &auto, patch)
|
||||
}
|
||||
|
||||
// intervalOrDefault gives the interval specified, or if missing, the default
|
||||
func intervalOrDefault(auto *imagev1.ImageUpdateAutomation) time.Duration {
|
||||
if auto.Spec.Interval.Duration < time.Second {
|
||||
return time.Second
|
||||
}
|
||||
return auto.Spec.Interval.Duration
|
||||
}
|
||||
|
||||
// durationSinceLastRun calculates how long it's been since the last
|
||||
// time the automation ran (which you can then use to find how long to
|
||||
// wait until the next run).
|
||||
func durationSinceLastRun(auto *imagev1.ImageUpdateAutomation, now time.Time) time.Duration {
|
||||
last := auto.Status.LastAutomationRunTime
|
||||
if last == nil {
|
||||
return time.Duration(math.MaxInt64) // a fairly long time
|
||||
}
|
||||
return now.Sub(last.Time)
|
||||
}
|
||||
|
||||
// automationsForGitRepo fetches all the automations that refer to a
|
||||
// particular source.GitRepository object.
|
||||
func (r *ImageUpdateAutomationReconciler) automationsForGitRepo(obj client.Object) []reconcile.Request {
|
||||
ctx := context.Background()
|
||||
var autoList imagev1.ImageUpdateAutomationList
|
||||
if err := r.List(ctx, &autoList, client.InNamespace(obj.GetNamespace()),
|
||||
client.MatchingFields{repoRefKey: obj.GetName()}); err != nil {
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, len(autoList.Items), len(autoList.Items))
|
||||
for i := range autoList.Items {
|
||||
reqs[i].NamespacedName.Name = autoList.Items[i].GetName()
|
||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
|
||||
// automationsForImagePolicy fetches all the automation objects that
|
||||
// might depend on a image policy object. Since the link is via
|
||||
// markers in the git repo, _any_ automation object in the same
|
||||
// namespace could be affected.
|
||||
func (r *ImageUpdateAutomationReconciler) automationsForImagePolicy(obj client.Object) []reconcile.Request {
|
||||
ctx := context.Background()
|
||||
var autoList imagev1.ImageUpdateAutomationList
|
||||
if err := r.List(ctx, &autoList, client.InNamespace(obj.GetNamespace())); err != nil {
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, len(autoList.Items), len(autoList.Items))
|
||||
for i := range autoList.Items {
|
||||
reqs[i].NamespacedName.Name = autoList.Items[i].GetName()
|
||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) getAuthOpts(ctx context.Context, repository *sourcev1.GitRepository) (*git.AuthOptions, error) {
|
||||
var data map[string][]byte
|
||||
if repository.Spec.SecretRef != nil {
|
||||
name := types.NamespacedName{
|
||||
Namespace: repository.GetNamespace(),
|
||||
Name: repository.Spec.SecretRef.Name,
|
||||
}
|
||||
|
||||
secret := &corev1.Secret{}
|
||||
err := r.Client.Get(ctx, name, secret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get secret '%s': %w", name.String(), err)
|
||||
}
|
||||
data = secret.Data
|
||||
}
|
||||
|
||||
u, err := url.Parse(repository.Spec.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse URL '%s': %w", repository.Spec.URL, err)
|
||||
}
|
||||
|
||||
opts, err := git.NewAuthOptions(*u, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to configure authentication options: %w", err)
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// getSigningEntity retrieves an OpenPGP entity referenced by the
|
||||
// provided imagev1.ImageUpdateAutomation for git commit signing
|
||||
func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context, auto imagev1.ImageUpdateAutomation) (*openpgp.Entity, error) {
|
||||
// get kubernetes secret
|
||||
secretName := types.NamespacedName{
|
||||
Namespace: auto.GetNamespace(),
|
||||
Name: auto.Spec.GitSpec.Commit.SigningKey.SecretRef.Name,
|
||||
}
|
||||
var secret corev1.Secret
|
||||
if err := r.Get(ctx, secretName, &secret); err != nil {
|
||||
return nil, fmt.Errorf("could not find signing key secret '%s': %w", secretName, err)
|
||||
}
|
||||
|
||||
// get data from secret
|
||||
data, ok := secret.Data[signingSecretKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("signing key secret '%s' does not contain a 'git.asc' key", secretName)
|
||||
}
|
||||
|
||||
// read entity from secret value
|
||||
entities, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read signing key from secret '%s': %w", secretName, err)
|
||||
}
|
||||
if len(entities) > 1 {
|
||||
return nil, fmt.Errorf("multiple entities read from secret '%s', could not determine which signing key to use", secretName)
|
||||
}
|
||||
|
||||
entity := entities[0]
|
||||
if entity.PrivateKey.Encrypted {
|
||||
passphrase, ok := secret.Data[signingPassphraseKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can not use passphrase protected signing key without '%s' field present in secret %s",
|
||||
signingPassphraseKey, secretName)
|
||||
}
|
||||
if err = entity.PrivateKey.Decrypt([]byte(passphrase)); err != nil {
|
||||
return nil, fmt.Errorf("could not decrypt private key of the signing key present in secret %s: %w", secretName, err)
|
||||
}
|
||||
}
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
// --- events, metrics
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) event(ctx context.Context, auto imagev1.ImageUpdateAutomation, severity, msg string) {
|
||||
eventtype := "Normal"
|
||||
if severity == eventv1.EventSeverityError {
|
||||
eventtype = "Warning"
|
||||
}
|
||||
r.EventRecorder.Eventf(&auto, eventtype, severity, msg)
|
||||
}
|
||||
|
||||
// --- updates
|
||||
|
||||
// updateAccordingToSetters updates files under the root by treating
|
||||
// the given image policies as kyaml setters.
|
||||
func updateAccordingToSetters(ctx context.Context, tracelog logr.Logger, inpath, outpath string, policies []imagev1_reflect.ImagePolicy) (update.Result, error) {
|
||||
return update.UpdateWithSetters(tracelog, inpath, outpath, policies)
|
||||
}
|
||||
|
||||
// templateMsg renders a msg template, returning the message or an error.
|
||||
func templateMsg(messageTemplate string, templateValues *TemplateData) (string, error) {
|
||||
if messageTemplate == "" {
|
||||
messageTemplate = defaultMessageTemplate
|
||||
}
|
||||
|
||||
// Includes only functions that are guaranteed to always evaluate to the same result for given input.
|
||||
// This removes the possibility of accidentally relying on where or when the template runs.
|
||||
// https://github.com/Masterminds/sprig/blob/3ac42c7bc5e4be6aa534e036fb19dde4a996da2e/functions.go#L70
|
||||
t, err := template.New("commit message").Funcs(sprig.HermeticTxtFuncMap()).Parse(messageTemplate)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to create commit message template from spec: %w", err)
|
||||
}
|
||||
|
||||
b := &strings.Builder{}
|
||||
if err := t.Execute(b, *templateValues); err != nil {
|
||||
return "", fmt.Errorf("failed to run template from spec: %w", err)
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
/surely/does/not/exist
|
File diff suppressed because it is too large
Load Diff
|
@ -19,7 +19,10 @@ limitations under the License.
|
|||
// states.
|
||||
package features
|
||||
|
||||
import feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||
import (
|
||||
"github.com/fluxcd/pkg/auth"
|
||||
feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||
)
|
||||
|
||||
const (
|
||||
// GitForcePushBranch enables the use of "force push" when push branches
|
||||
|
@ -31,6 +34,9 @@ const (
|
|||
// GitAllBranchReferences enables the download of all branch head references
|
||||
// when push branches are configured. When enabled fixes fluxcd/flux2#3384.
|
||||
GitAllBranchReferences = "GitAllBranchReferences"
|
||||
// GitSparseCheckout enables the use of sparse checkout when pulling source from
|
||||
// Git repositories.
|
||||
GitSparseCheckout = "GitSparseCheckout"
|
||||
// CacheSecretsAndConfigMaps controls whether Secrets and ConfigMaps should
|
||||
// be cached.
|
||||
//
|
||||
|
@ -52,11 +58,19 @@ var features = map[string]bool{
|
|||
// opt-out from v0.28
|
||||
GitAllBranchReferences: true,
|
||||
|
||||
// GitSparseCheckout
|
||||
// opt-in from v0.42
|
||||
GitSparseCheckout: false,
|
||||
|
||||
// CacheSecretsAndConfigMaps
|
||||
// opt-in from v0.29
|
||||
CacheSecretsAndConfigMaps: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
auth.SetFeatureGates(features)
|
||||
}
|
||||
|
||||
// FeatureGates contains a list of all supported feature gates and
|
||||
// their default values.
|
||||
func FeatureGates() map[string]bool {
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -7,4 +7,4 @@ spec:
|
|||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.2.0 # SETTER_SITE
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-no
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-yes
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.1 # SETTER_SITE
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-no
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-yes
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -0,0 +1,342 @@
|
|||
/*
|
||||
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
|
||||
}
|
|
@ -0,0 +1,685 @@
|
|||
/*
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,441 @@
|
|||
/*
|
||||
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
|
@ -0,0 +1,10 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -0,0 +1,480 @@
|
|||
/*
|
||||
Copyright 2024 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/go-git/go-billy/v5/osfs"
|
||||
extgogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/cache"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
|
||||
"github.com/fluxcd/pkg/gittestserver"
|
||||
|
||||
"github.com/fluxcd/image-automation-controller/internal/constants"
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
const (
|
||||
signingSecretKey = "git.asc"
|
||||
signingPassphraseKey = "passphrase"
|
||||
)
|
||||
|
||||
func CheckoutBranch(g *WithT, repo *extgogit.Repository, branch string) {
|
||||
g.THelper()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func ReplaceMarker(path string, policyKey types.NamespacedName) error {
|
||||
return ReplaceMarkerWithMarker(path, policyKey, "SETTER_SITE")
|
||||
}
|
||||
|
||||
func ReplaceMarkerWithMarker(path string, policyKey types.NamespacedName, marker string) error {
|
||||
filebytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newfilebytes := bytes.ReplaceAll(filebytes, []byte(marker), []byte(setterRef(policyKey)))
|
||||
if err = os.WriteFile(path, newfilebytes, os.FileMode(0666)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setterRef(name types.NamespacedName) string {
|
||||
return fmt.Sprintf(`{"%s": "%s:%s"}`, constants.SetterShortHand, name.Namespace, name.Name)
|
||||
}
|
||||
|
||||
func CommitInRepo(ctx context.Context, g *WithT, repoURL, branch, remote, msg string, changeFiles func(path string)) plumbing.Hash {
|
||||
g.THelper()
|
||||
|
||||
repo, cloneDir, err := Clone(ctx, repoURL, branch, remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer func() { os.RemoveAll(cloneDir) }()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
changeFiles(wt.Filesystem.Root())
|
||||
|
||||
id := CommitWorkDir(g, repo, branch, msg)
|
||||
|
||||
origin, err := repo.Remote(remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(origin.Push(&extgogit.PushOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branch))},
|
||||
})).To(Succeed())
|
||||
return id
|
||||
}
|
||||
|
||||
func WaitForNewHead(g *WithT, repo *extgogit.Repository, branch, remote, preChangeHash string) {
|
||||
g.THelper()
|
||||
|
||||
var commitToResetTo *object.Commit
|
||||
|
||||
origin, err := repo.Remote(remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Now try to fetch new commits from that remote branch
|
||||
g.Eventually(func() bool {
|
||||
err := origin.Fetch(&extgogit.FetchOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branch))},
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
remoteHeadRef, err := repo.Head()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
remoteHeadHash := remoteHeadRef.Hash()
|
||||
|
||||
if preChangeHash != remoteHeadHash.String() {
|
||||
commitToResetTo, _ = repo.CommitObject(remoteHeadHash)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, 10*time.Second, time.Second).Should(BeTrue())
|
||||
|
||||
if commitToResetTo != nil {
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// New commits in the remote branch -- reset the working tree head
|
||||
// to that. Note this does not create a local branch tracking the
|
||||
// remote, so it is a detached head.
|
||||
g.Expect(wt.Reset(&extgogit.ResetOptions{
|
||||
Commit: commitToResetTo.Hash,
|
||||
Mode: extgogit.HardReset,
|
||||
})).To(Succeed())
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise a git server with a repo including the files in dir.
|
||||
func InitGitRepo(g *WithT, gitServer *gittestserver.GitServer, fixture, branch, repoPath string) *extgogit.Repository {
|
||||
g.THelper()
|
||||
|
||||
workDir, err := securejoin.SecureJoin(gitServer.Root(), repoPath)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
repo := InitGitRepoPlain(g, fixture, workDir)
|
||||
|
||||
headRef, err := repo.Head()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
ref := plumbing.NewHashReference(
|
||||
plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branch)),
|
||||
headRef.Hash())
|
||||
|
||||
g.Expect(repo.Storer.SetReference(ref)).ToNot(HaveOccurred())
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func InitGitRepoPlain(g *WithT, fixture, repoPath string) *extgogit.Repository {
|
||||
g.THelper()
|
||||
|
||||
wt := osfs.New(repoPath)
|
||||
dot := osfs.New(filepath.Join(repoPath, extgogit.GitDirName))
|
||||
storer := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
|
||||
|
||||
repo, err := extgogit.Init(storer, wt)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(copyDir(fixture, repoPath)).ToNot(HaveOccurred())
|
||||
|
||||
_ = CommitWorkDir(g, repo, "main", "Initial commit")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func HeadFromBranch(repo *extgogit.Repository, branchName string) (*object.Commit, error) {
|
||||
ref, err := repo.Storer.Reference(plumbing.ReferenceName("refs/heads/" + branchName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo.CommitObject(ref.Hash())
|
||||
}
|
||||
|
||||
func CommitWorkDir(g *WithT, repo *extgogit.Repository, branchName, message string) plumbing.Hash {
|
||||
g.THelper()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Checkout to an existing branch. If this is the first commit,
|
||||
// this is a no-op.
|
||||
_ = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.ReferenceName("refs/heads/" + branchName),
|
||||
})
|
||||
|
||||
status, err := wt.Status()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
for file := range status {
|
||||
wt.Add(file)
|
||||
}
|
||||
|
||||
sig := mockSignature(time.Now())
|
||||
c, err := wt.Commit(message, &extgogit.CommitOptions{
|
||||
All: true,
|
||||
Author: sig,
|
||||
Committer: sig,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
_, err = repo.Branch(branchName)
|
||||
if err == extgogit.ErrBranchNotFound {
|
||||
ref := plumbing.NewHashReference(
|
||||
plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branchName)), c)
|
||||
err = repo.Storer.SetReference(ref)
|
||||
}
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Now the target branch exists, we can checkout to it.
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.ReferenceName("refs/heads/" + branchName),
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func TagCommit(g *WithT, repo *extgogit.Repository, commit plumbing.Hash, annotated bool, tag string, time time.Time) (*plumbing.Reference, error) {
|
||||
g.THelper()
|
||||
|
||||
var opts *extgogit.CreateTagOptions
|
||||
if annotated {
|
||||
opts = &extgogit.CreateTagOptions{
|
||||
Tagger: mockSignature(time),
|
||||
Message: "Annotated tag for: " + tag,
|
||||
}
|
||||
}
|
||||
return repo.CreateTag(tag, commit, opts)
|
||||
}
|
||||
|
||||
func copyDir(src string, dest string) error {
|
||||
file, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !file.IsDir() {
|
||||
return fmt.Errorf("source %q must be a directory", file.Name())
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(dest, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
srcFile := filepath.Join(src, f.Name())
|
||||
destFile := filepath.Join(dest, f.Name())
|
||||
|
||||
if f.IsDir() {
|
||||
if err = copyDir(srcFile, destFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.IsDir() {
|
||||
// ignore symlinks
|
||||
if f.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.WriteFile(destFile, content, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func BranchRefName(branch string) string {
|
||||
return fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)
|
||||
}
|
||||
|
||||
func mockSignature(time time.Time) *object.Signature {
|
||||
return &object.Signature{
|
||||
Name: "Jane Doe",
|
||||
Email: "author@example.com",
|
||||
When: time,
|
||||
}
|
||||
}
|
||||
|
||||
func Clone(ctx context.Context, repoURL, branchName, remote string) (*extgogit.Repository, string, error) {
|
||||
dir, err := os.MkdirTemp("", "iac-clone-*")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
opts := &extgogit.CloneOptions{
|
||||
URL: repoURL,
|
||||
RemoteName: remote,
|
||||
ReferenceName: plumbing.NewBranchReferenceName(branchName),
|
||||
}
|
||||
|
||||
wt := osfs.New(dir, osfs.WithBoundOS())
|
||||
dot := osfs.New(filepath.Join(dir, extgogit.GitDirName), osfs.WithBoundOS())
|
||||
storer := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
|
||||
|
||||
repo, err := extgogit.Clone(storer, wt, opts)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
w, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
err = w.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branchName),
|
||||
Create: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return repo, dir, nil
|
||||
}
|
||||
|
||||
func CommitIdFromBranch(repo *extgogit.Repository, branchName string) string {
|
||||
commitId := ""
|
||||
head, err := HeadFromBranch(repo, branchName)
|
||||
|
||||
if err == nil {
|
||||
commitId = head.Hash.String()
|
||||
}
|
||||
return commitId
|
||||
}
|
||||
|
||||
func GetRemoteHead(repo *extgogit.Repository, branchName, remote string) (plumbing.Hash, error) {
|
||||
rmt, err := repo.Remote(remote)
|
||||
if err != nil {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
err = rmt.Fetch(&extgogit.FetchOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branchName))},
|
||||
})
|
||||
if err != nil && !errors.Is(err, extgogit.NoErrAlreadyUpToDate) {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
remoteHeadRef, err := HeadFromBranch(repo, branchName)
|
||||
if err != nil {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
return remoteHeadRef.Hash, nil
|
||||
}
|
||||
|
||||
// SetUpGitTestServer creates and returns a git test server. The caller must
|
||||
// ensure it's stopped and cleaned up.
|
||||
func SetUpGitTestServer(g *WithT) *gittestserver.GitServer {
|
||||
g.THelper()
|
||||
|
||||
gitServer, err := gittestserver.NewTempGitServer()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
username := rand.String(5)
|
||||
password := rand.String(5)
|
||||
|
||||
gitServer.Auth(username, password)
|
||||
gitServer.AutoCreate()
|
||||
g.Expect(gitServer.StartHTTP()).ToNot(HaveOccurred())
|
||||
gitServer.KeyDir(filepath.Join(gitServer.Root(), "keys"))
|
||||
g.Expect(gitServer.ListenSSH()).ToNot(HaveOccurred())
|
||||
return gitServer
|
||||
}
|
||||
|
||||
func GetSigningKeyPairSecret(g *WithT, name, namespace string) (*corev1.Secret, *openpgp.Entity) {
|
||||
g.THelper()
|
||||
|
||||
passphrase := "abcde12345"
|
||||
pgpEntity, key := GetSigningKeyPair(g, passphrase)
|
||||
|
||||
// Create the secret containing signing key.
|
||||
sec := &corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
signingSecretKey: key,
|
||||
signingPassphraseKey: []byte(passphrase),
|
||||
},
|
||||
}
|
||||
sec.Name = name
|
||||
sec.Namespace = namespace
|
||||
return sec, pgpEntity
|
||||
}
|
||||
|
||||
func GetSigningKeyPair(g *WithT, passphrase string) (*openpgp.Entity, []byte) {
|
||||
g.THelper()
|
||||
|
||||
pgpEntity, err := openpgp.NewEntity("", "", "", nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Configure OpenPGP armor encoder.
|
||||
b := bytes.NewBuffer(nil)
|
||||
w, err := armor.Encode(b, openpgp.PrivateKeyType, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
// Serialize private key.
|
||||
g.Expect(pgpEntity.SerializePrivate(w, nil)).To(Succeed())
|
||||
g.Expect(w.Close()).To(Succeed())
|
||||
|
||||
if passphrase != "" {
|
||||
g.Expect(pgpEntity.PrivateKey.Encrypt([]byte(passphrase))).To(Succeed())
|
||||
}
|
||||
|
||||
return pgpEntity, b.Bytes()
|
||||
}
|
||||
|
||||
func ImageToRef(image string) *imagev1_reflect.ImageRef {
|
||||
var digest string
|
||||
|
||||
if idx := strings.LastIndex(image, "@"); idx != -1 {
|
||||
image, digest = image[:idx], image[idx+1:]
|
||||
}
|
||||
|
||||
var tag string
|
||||
|
||||
if idx := strings.LastIndex(image, ":"); idx != -1 {
|
||||
image, tag = image[:idx], image[idx+1:]
|
||||
}
|
||||
|
||||
return &imagev1_reflect.ImageRef{
|
||||
Name: image,
|
||||
Tag: tag,
|
||||
Digest: digest,
|
||||
}
|
||||
}
|
104
main.go
104
main.go
|
@ -26,11 +26,17 @@ import (
|
|||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
"k8s.io/utils/pointer"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
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"
|
||||
"github.com/fluxcd/pkg/runtime/acl"
|
||||
"github.com/fluxcd/pkg/runtime/client"
|
||||
helper "github.com/fluxcd/pkg/runtime/controller"
|
||||
|
@ -38,23 +44,22 @@ import (
|
|||
feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||
"github.com/fluxcd/pkg/runtime/leaderelection"
|
||||
"github.com/fluxcd/pkg/runtime/logger"
|
||||
"github.com/fluxcd/pkg/runtime/metrics"
|
||||
"github.com/fluxcd/pkg/runtime/pprof"
|
||||
"github.com/fluxcd/pkg/runtime/probes"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
"github.com/fluxcd/image-automation-controller/internal/features"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/features"
|
||||
|
||||
// +kubebuilder:scaffold:imports
|
||||
"github.com/fluxcd/image-automation-controller/internal/controllers"
|
||||
"github.com/fluxcd/image-automation-controller/internal/controller"
|
||||
)
|
||||
|
||||
const (
|
||||
controllerName = "image-automation-controller"
|
||||
|
||||
// recoverPanic indicates whether panic caused by reconciles should be recovered.
|
||||
recoverPanic = true
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -71,6 +76,10 @@ func init() {
|
|||
}
|
||||
|
||||
func main() {
|
||||
const (
|
||||
tokenCacheDefaultMaxSize = 100
|
||||
)
|
||||
|
||||
var (
|
||||
metricsAddr string
|
||||
eventsAddr string
|
||||
|
@ -83,6 +92,7 @@ func main() {
|
|||
featureGates feathelper.FeatureGates
|
||||
watchOptions helper.WatchOptions
|
||||
concurrent int
|
||||
tokenCacheOptions cache.TokenFlags
|
||||
)
|
||||
|
||||
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
|
@ -101,6 +111,7 @@ func main() {
|
|||
rateLimiterOptions.BindFlags(flag.CommandLine)
|
||||
featureGates.BindFlags(flag.CommandLine)
|
||||
watchOptions.BindFlags(flag.CommandLine)
|
||||
tokenCacheOptions.BindFlags(flag.CommandLine, tokenCacheDefaultMaxSize)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
|
@ -113,6 +124,14 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch enabled, err := features.Enabled(auth.FeatureGateObjectLevelWorkloadIdentity); {
|
||||
case err != nil:
|
||||
setupLog.Error(err, "unable to check feature gate "+auth.FeatureGateObjectLevelWorkloadIdentity)
|
||||
os.Exit(1)
|
||||
case enabled:
|
||||
auth.EnableObjectLevelWorkloadIdentity()
|
||||
}
|
||||
|
||||
watchNamespace := ""
|
||||
if !watchOptions.AllNamespaces {
|
||||
watchNamespace = os.Getenv("RUNTIME_NAMESPACE")
|
||||
|
@ -136,39 +155,53 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
selectingCacheFunc := cache.BuilderWithOptions(cache.Options{
|
||||
SelectorsByObject: cache.SelectorsByObject{
|
||||
&imagev1.ImageUpdateAutomation{}: {Label: watchSelector},
|
||||
},
|
||||
})
|
||||
|
||||
leaderElectionID := fmt.Sprintf("%s-leader-election", controllerName)
|
||||
if watchOptions.LabelSelector != "" {
|
||||
leaderElectionID = leaderelection.GenerateID(leaderElectionID, watchOptions.LabelSelector)
|
||||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
|
||||
mgrConfig := ctrl.Options{
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
HealthProbeBindAddress: healthAddr,
|
||||
Port: 9443,
|
||||
LeaderElection: leaderElectionOptions.Enable,
|
||||
LeaderElectionReleaseOnCancel: leaderElectionOptions.ReleaseOnCancel,
|
||||
LeaseDuration: &leaderElectionOptions.LeaseDuration,
|
||||
NewCache: selectingCacheFunc,
|
||||
RenewDeadline: &leaderElectionOptions.RenewDeadline,
|
||||
RetryPeriod: &leaderElectionOptions.RetryPeriod,
|
||||
LeaderElectionID: leaderElectionID,
|
||||
Namespace: watchNamespace,
|
||||
ClientDisableCacheFor: disableCacheFor,
|
||||
})
|
||||
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)
|
||||
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 {
|
||||
|
@ -176,17 +209,32 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
metricsH := helper.MustMakeMetrics(mgr)
|
||||
metricsH := helper.NewMetrics(mgr, metrics.MustMakeRecorder(), imagev1.ImageUpdateAutomationFinalizer)
|
||||
|
||||
if err := (&controllers.ImageUpdateAutomationReconciler{
|
||||
var tokenCache *cache.TokenCache
|
||||
if tokenCacheOptions.MaxSize > 0 {
|
||||
var err error
|
||||
tokenCache, err = cache.NewTokenCache(tokenCacheOptions.MaxSize,
|
||||
cache.WithMaxDuration(tokenCacheOptions.MaxDuration),
|
||||
cache.WithMetricsRegisterer(ctrlmetrics.Registry),
|
||||
cache.WithMetricsPrefix("gotk_token_"))
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to create token cache")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := ctrl.SetupSignalHandler()
|
||||
|
||||
if err := (&controller.ImageUpdateAutomationReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
EventRecorder: eventRecorder,
|
||||
Metrics: metricsH,
|
||||
NoCrossNamespaceRef: aclOptions.NoCrossNamespaceRefs,
|
||||
}).SetupWithManager(mgr, controllers.ImageUpdateAutomationReconcilerOptions{
|
||||
MaxConcurrentReconciles: concurrent,
|
||||
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
|
||||
RecoverPanic: recoverPanic,
|
||||
ControllerName: controllerName,
|
||||
}).SetupWithManager(ctx, mgr, controller.ImageUpdateAutomationReconcilerOptions{
|
||||
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
|
||||
TokenCache: tokenCache,
|
||||
}); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "ImageUpdateAutomation")
|
||||
os.Exit(1)
|
||||
|
@ -194,7 +242,7 @@ func main() {
|
|||
// +kubebuilder:scaffold:builder
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
if err := mgr.Start(ctx); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ func (r *ScreeningLocalReader) Read() ([]*yaml.RNode, error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
if ext := filepath.Ext(p); ext != ".yaml" && ext != ".yml" {
|
||||
if ext := filepath.Ext(p); ext != ".yaml" && ext != ".yml" && filepath.Base(p) != "Kustomization" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -33,11 +33,12 @@ func TestScreeningLocalReader(t *testing.T) {
|
|||
}
|
||||
nodes, err := r.Read()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
// the test fixture has three files that contain the marker:
|
||||
// - otherns.yaml
|
||||
// the test fixture has four files that contain the marker:
|
||||
// - marked.yaml
|
||||
// - kustomization.yaml
|
||||
g.Expect(len(nodes)).To(Equal(3))
|
||||
// - otherns.yaml
|
||||
// - kustomization.yml
|
||||
// - Kustomization
|
||||
g.Expect(len(nodes)).To(Equal(4))
|
||||
filesSeen := map[string]struct{}{}
|
||||
for i := range nodes {
|
||||
path, _, err := kioutil.GetFileAnnotations(nodes[i])
|
||||
|
@ -45,9 +46,10 @@ func TestScreeningLocalReader(t *testing.T) {
|
|||
filesSeen[path] = struct{}{}
|
||||
}
|
||||
g.Expect(filesSeen).To(Equal(map[string]struct{}{
|
||||
"marked.yaml": {},
|
||||
"kustomization.yaml": {},
|
||||
"otherns.yaml": {},
|
||||
"marked.yaml": {},
|
||||
"otherns.yaml": {},
|
||||
"kustomization.yml": {},
|
||||
"Kustomization": {},
|
||||
}))
|
||||
|
||||
}
|
||||
|
|
|
@ -17,11 +17,12 @@ 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"
|
||||
)
|
||||
|
||||
|
@ -112,8 +113,17 @@ 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 *setters2.CliExtension, sch *spec.Schema) (bool, error) {
|
||||
func (s *SetAllCallback) set(field *yaml.RNode, ext *extension, sch *spec.Schema) (bool, error) {
|
||||
// check full setter
|
||||
if ext.Setter == nil {
|
||||
return false, nil
|
||||
|
@ -139,7 +149,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 := setters2.GetExtFromSchema(fieldSchema.Schema)
|
||||
ext, err := getExtFromSchema(fieldSchema.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -152,3 +162,19 @@ 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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -96,3 +96,66 @@ 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
|
||||
}
|
||||
|
|
|
@ -92,3 +92,80 @@ 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",
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package update
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
|
@ -29,21 +28,19 @@ import (
|
|||
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||
"sigs.k8s.io/kustomize/kyaml/sets"
|
||||
"sigs.k8s.io/kustomize/kyaml/setters2"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
|
||||
"github.com/fluxcd/image-automation-controller/internal/constants"
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
const (
|
||||
// SetterShortHand is a shorthand that can be used to mark
|
||||
// setters; instead of
|
||||
// # { "$ref": "#/definitions/
|
||||
SetterShortHand = "$imagepolicy"
|
||||
// This is preserved from setters2
|
||||
K8sCliExtensionKey = "x-k8s-cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
fieldmeta.SetShortHandRef(SetterShortHand)
|
||||
fieldmeta.SetShortHandRef(constants.SetterShortHand)
|
||||
// this prevents the global schema, should it be initialised, from
|
||||
// parsing all the Kubernetes openAPI definitions, which is not
|
||||
// necessary.
|
||||
|
@ -54,6 +51,15 @@ 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
|
||||
|
@ -97,13 +103,15 @@ func UpdateWithSetters(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) {
|
||||
setAllCallback := func(file, setterName string, node *yaml.RNode, old, new string) {
|
||||
ref, ok := imageRefs[setterName]
|
||||
if !ok {
|
||||
return
|
||||
|
@ -115,6 +123,15 @@ func UpdateWithSetters(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{
|
||||
|
@ -122,7 +139,7 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
}
|
||||
result.Files[file] = fileres
|
||||
}
|
||||
objres, ok := fileres.Objects[oid]
|
||||
objres := fileres.Objects[oid]
|
||||
for _, n := range objres {
|
||||
if n == ref {
|
||||
return
|
||||
|
@ -134,7 +151,7 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
|
||||
defs := map[string]spec.Schema{}
|
||||
for _, policy := range policies {
|
||||
if policy.Status.LatestImage == "" {
|
||||
if policy.Status.LatestRef == nil {
|
||||
continue
|
||||
}
|
||||
// Using strict validation would mean any image that omits the
|
||||
|
@ -143,10 +160,10 @@ func UpdateWithSetters(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.LatestImage
|
||||
image := policy.Status.LatestRef.String()
|
||||
r, err := name.ParseReference(image, name.WeakValidation)
|
||||
if err != nil {
|
||||
return Result{}, fmt.Errorf("encountered invalid image ref %q: %w", policy.Status.LatestImage, err)
|
||||
return ResultV2{}, fmt.Errorf("encountered invalid image ref %q: %w", image, err)
|
||||
}
|
||||
ref := imageRef{
|
||||
Reference: r,
|
||||
|
@ -156,15 +173,13 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
},
|
||||
}
|
||||
|
||||
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)
|
||||
tag := policy.Status.LatestRef.Tag
|
||||
name := policy.Status.LatestRef.Name
|
||||
digest := policy.Status.LatestRef.Digest
|
||||
|
||||
imageSetter := fmt.Sprintf("%s:%s", policy.GetNamespace(), policy.GetName())
|
||||
tracelog.Info("adding setter", "name", imageSetter)
|
||||
defs[fieldmeta.SetterDefinitionPrefix+imageSetter] = setterSchema(imageSetter, policy.Status.LatestImage)
|
||||
defs[fieldmeta.SetterDefinitionPrefix+imageSetter] = setterSchema(imageSetter, image)
|
||||
imageRefs[imageSetter] = ref
|
||||
|
||||
tagSetter := imageSetter + ":tag"
|
||||
|
@ -172,11 +187,15 @@ func UpdateWithSetters(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
|
||||
|
@ -184,7 +203,7 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
// get ready with the reader and writer
|
||||
reader := &ScreeningLocalReader{
|
||||
Path: inpath,
|
||||
Token: fmt.Sprintf("%q", SetterShortHand),
|
||||
Token: fmt.Sprintf("%q", constants.SetterShortHand),
|
||||
Trace: tracelog,
|
||||
}
|
||||
writer := &kio.LocalPackageWriter{
|
||||
|
@ -202,9 +221,12 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
// go!
|
||||
err := pipeline.Execute()
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
return ResultV2{}, err
|
||||
}
|
||||
return result, nil
|
||||
|
||||
// Combine the results.
|
||||
resultV2.ImageResult = result
|
||||
return resultV2, nil
|
||||
}
|
||||
|
||||
// setAll returns a kio.Filter using the supplied SetAllCallback
|
||||
|
@ -213,7 +235,7 @@ func UpdateWithSetters(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)) kio.Filter {
|
||||
func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, setterName string, node *yaml.RNode, old, new string)) kio.Filter {
|
||||
filter := &SetAllCallback{
|
||||
SettersSchema: schema,
|
||||
Trace: tracelog,
|
||||
|
@ -229,7 +251,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])
|
||||
callback(path, setter, nodes[i], oldValue, newValue)
|
||||
filesToUpdate.Insert(path)
|
||||
}
|
||||
}
|
||||
|
@ -256,7 +278,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(setters2.K8sCliExtensionKey, map[string]interface{}{
|
||||
schema.Extensions.Add(K8sCliExtensionKey, map[string]interface{}{
|
||||
"setter": map[string]string{
|
||||
"name": name,
|
||||
"value": value,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# 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"}
|
|
@ -1,9 +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"}
|
|
@ -0,0 +1,28 @@
|
|||
# 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"}
|
|
@ -0,0 +1,9 @@
|
|||
# This is not intended to be a working kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- irrelevant.yaml
|
||||
images:
|
||||
- name: container
|
||||
newName: replaced # {"$imagepolicy": "automation-ns:policy:name"}
|
||||
newTag: v1 # {"$imagepolicy": "automation-ns:policy:tag"}
|
|
@ -1,9 +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"}
|
|
@ -0,0 +1,28 @@
|
|||
# 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"}
|
|
@ -17,8 +17,6 @@ limitations under the License.
|
|||
package update
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
|
@ -28,6 +26,7 @@ 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"
|
||||
)
|
||||
|
@ -37,29 +36,35 @@ func TestUpdateWithSetters(t *testing.T) {
|
|||
|
||||
policies := []imagev1_reflect.ImagePolicy{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{ // name matches marker used in testdata/setters/{original,expected}
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "automation-ns",
|
||||
Name: "policy",
|
||||
},
|
||||
Status: imagev1_reflect.ImagePolicyStatus{
|
||||
LatestImage: "index.repo.fake/updated:v1.0.1",
|
||||
LatestRef: testutil.ImageToRef("index.repo.fake/updated:v1.0.1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{ // name matches marker used in testdata/setters/{original,expected}
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "automation-ns",
|
||||
Name: "unchanged",
|
||||
},
|
||||
Status: imagev1_reflect.ImagePolicyStatus{
|
||||
LatestImage: "image:v1.0.0",
|
||||
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"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tmp, err := ioutil.TempDir("", "gotest")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
tmp := t.TempDir()
|
||||
result, err := UpdateWithSetters(logr.Discard(), "testdata/setters/original", tmp, policies)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
test.ExpectMatchingDirectories(g, tmp, "testdata/setters/expected")
|
||||
|
@ -80,15 +85,30 @@ 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.yaml": {
|
||||
"kustomization.yml": {
|
||||
Objects: map[ObjectIdentifier][]ImageRef{
|
||||
kustomizeResourceID: {
|
||||
expectedImageRef,
|
||||
expectedImageRefDigest,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Kustomization": {
|
||||
Objects: map[ObjectIdentifier][]ImageRef{
|
||||
kustomizeResourceID: {
|
||||
expectedImageRef,
|
||||
|
@ -106,4 +126,65 @@ 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))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
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
|
||||
|
|
|
@ -21,8 +21,8 @@ set -euxo pipefail
|
|||
|
||||
# Some tests requires embedded resources. Embedding does not allow
|
||||
# for traversing into ascending dirs, therefore we copy those contents here:
|
||||
mkdir -p internal/controllers/testdata/crd
|
||||
cp config/crd/bases/*.yaml internal/controllers/testdata/crd
|
||||
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.
|
||||
|
@ -32,10 +32,10 @@ SOURCE_VER=$(go list -m github.com/fluxcd/source-controller/api | awk '{print $2
|
|||
# Pulls image-reflector-controller/api's version set in go.mod.
|
||||
REFLECTOR_VER=$(go list -m github.com/fluxcd/image-reflector-controller/api | awk '{print $2}')
|
||||
|
||||
if [ -d "../../internal/controllers/testdata/crds" ]; then
|
||||
cp ../../internal/controllers/testdata/crds/*.yaml testdata/crds
|
||||
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/controllers/testdata/crd/gitrepositories.yaml
|
||||
curl -s --fail https://raw.githubusercontent.com/fluxcd/image-reflector-controller/${REFLECTOR_VER}/config/crd/bases/image.toolkit.fluxcd.io_imagepolicies.yaml -o internal/controllers/testdata/crd/imagepolicies.yaml
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue