Compare commits
269 Commits
v2.0.0-rc.
...
flux-audit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f79b4dd57 | ||
|
|
f20fe76168 | ||
|
|
b596aefb91 | ||
|
|
c5b5263b1b | ||
|
|
765ebbacfe | ||
|
|
2fb132bdc5 | ||
|
|
80efd29ec0 | ||
|
|
4cb89adec4 | ||
|
|
aca461912c | ||
|
|
e645402ced | ||
|
|
e184ef2618 | ||
|
|
0fcda45659 | ||
|
|
c5dd6a18fa | ||
|
|
3c8072d0e6 | ||
|
|
cbccb8c46a | ||
|
|
e73d1acb20 | ||
|
|
1b239fbc82 | ||
|
|
0ad4db9f82 | ||
|
|
659ce798c9 | ||
|
|
6dd0cbfadd | ||
|
|
e7c015d24c | ||
|
|
dcbca6b1bf | ||
|
|
28971edc07 | ||
|
|
9cd4a7215f | ||
|
|
2f15ad972b | ||
|
|
10cddb457f | ||
|
|
7771107e4d | ||
|
|
5879d8575a | ||
|
|
80810bdc0f | ||
|
|
e3605acc13 | ||
|
|
cbce9b5e26 | ||
|
|
1ff76bd4a5 | ||
|
|
920fea7d1b | ||
|
|
adc04651cf | ||
|
|
0eba9662e6 | ||
|
|
7949135a74 | ||
|
|
025fe9ced1 | ||
|
|
5f1fe306bb | ||
|
|
f137263fe9 | ||
|
|
ba1180ad4d | ||
|
|
e3f6f0f8b3 | ||
|
|
f5d3283cff | ||
|
|
3fdb292381 | ||
|
|
22134b1233 | ||
|
|
ce3e264c7d | ||
|
|
5b22207c98 | ||
|
|
3278a0782f | ||
|
|
43c2359705 | ||
|
|
a2a3b4f00f | ||
|
|
3c495861a1 | ||
|
|
b67a46371b | ||
|
|
16d352b15b | ||
|
|
ac95ac0653 | ||
|
|
840e717b72 | ||
|
|
ae0c3c8020 | ||
|
|
b46e298b4d | ||
|
|
e9d4b42b12 | ||
|
|
771b7ab98a | ||
|
|
a51ede681f | ||
|
|
be03ca3b5d | ||
|
|
08cb3858ed | ||
|
|
39d4270f32 | ||
|
|
42372d9ef6 | ||
|
|
e9aa53d2ed | ||
|
|
8f7ed74913 | ||
|
|
2c090dbdf6 | ||
|
|
084fb6318d | ||
|
|
1daa7a8aa4 | ||
|
|
cf78e029aa | ||
|
|
a337a7ec73 | ||
|
|
630ca340dd | ||
|
|
e12839567e | ||
|
|
524a729f5d | ||
|
|
d3eacd4c20 | ||
|
|
f9e7190a04 | ||
|
|
15a63e3f2e | ||
|
|
1cba3e4476 | ||
|
|
5c567a1ea8 | ||
|
|
0c47d738a9 | ||
|
|
ebace983b9 | ||
|
|
1654791feb | ||
|
|
c20a57f1df | ||
|
|
1fc463c065 | ||
|
|
904226fcf3 | ||
|
|
c721474e0b | ||
|
|
1902f4af0d | ||
|
|
bc90e7cf01 | ||
|
|
0d18dc128a | ||
|
|
8cd7d8c5d2 | ||
|
|
9b1e160798 | ||
|
|
ab18cfe1a2 | ||
|
|
037562bf7b | ||
|
|
2d1937a5c8 | ||
|
|
c5723821da | ||
|
|
dbb9ea303d | ||
|
|
47c8e5f44d | ||
|
|
aff3365750 | ||
|
|
d61efd1e2c | ||
|
|
5d1cadcd5e | ||
|
|
ffe5657367 | ||
|
|
a9a67a27e7 | ||
|
|
c2e526ca57 | ||
|
|
7141271bae | ||
|
|
3b637a5125 | ||
|
|
cdc1c98a11 | ||
|
|
ab94c8064c | ||
|
|
e63ddb99de | ||
|
|
7c1b897919 | ||
|
|
f6b0c6e7ef | ||
|
|
1730f3c46b | ||
|
|
a814487d4b | ||
|
|
cd90bc2c92 | ||
|
|
709b17ce59 | ||
|
|
39fa7d5502 | ||
|
|
29f77d2cb3 | ||
|
|
22cf986a79 | ||
|
|
d80b697fbd | ||
|
|
8b9aaad20a | ||
|
|
4080d5807a | ||
|
|
7c2072eed1 | ||
|
|
d21e779b9f | ||
|
|
f7e5223533 | ||
|
|
525bd21cd1 | ||
|
|
8df27d8c3a | ||
|
|
6464d6c7b4 | ||
|
|
2fc9d73c5f | ||
|
|
b32051df53 | ||
|
|
bf36a29ca2 | ||
|
|
a2ac94b625 | ||
|
|
c81afa6993 | ||
|
|
4fa93ec4d6 | ||
|
|
00c6ac81b9 | ||
|
|
8801031f06 | ||
|
|
2a033215a4 | ||
|
|
8214fefde6 | ||
|
|
4cdb75b74d | ||
|
|
eac82585ad | ||
|
|
2c76c70205 | ||
|
|
a9e09b856f | ||
|
|
c03a0b7f87 | ||
|
|
0ab8740832 | ||
|
|
aa1eae22c7 | ||
|
|
4f3b34f86b | ||
|
|
8435cb8df9 | ||
|
|
0d457d6d11 | ||
|
|
484015ceea | ||
|
|
1b5c4245df | ||
|
|
ce68a06436 | ||
|
|
7273059cb9 | ||
|
|
a03ea8ace3 | ||
|
|
d6cbfa39f8 | ||
|
|
a2e4cbbfe2 | ||
|
|
23518953d0 | ||
|
|
2716ca449e | ||
|
|
ecb1ad6ca5 | ||
|
|
3fa7af12e0 | ||
|
|
12efb1967e | ||
|
|
56b1e80758 | ||
|
|
baf874ea67 | ||
|
|
28262f59d3 | ||
|
|
44d69d6fc0 | ||
|
|
4d76ff4e6a | ||
|
|
1eaf259e52 | ||
|
|
bca1fa0968 | ||
|
|
bd79884d84 | ||
|
|
3b42b200d3 | ||
|
|
dad4a20fa7 | ||
|
|
90d95988aa | ||
|
|
e88577fe52 | ||
|
|
6fa495b843 | ||
|
|
3311bfd3ca | ||
|
|
cfd4d285da | ||
|
|
c751bf6bdb | ||
|
|
6f94844a35 | ||
|
|
f74d097837 | ||
|
|
0a58b0cdad | ||
|
|
6f94ec728f | ||
|
|
e3747209eb | ||
|
|
36b39a50a4 | ||
|
|
d9c7ff8685 | ||
|
|
625d865625 | ||
|
|
00c6bd0240 | ||
|
|
506da2466b | ||
|
|
9ea0a535ea | ||
|
|
25d2a3cdf1 | ||
|
|
9fa59df798 | ||
|
|
93c0467f91 | ||
|
|
c377fe7651 | ||
|
|
9ed24522bb | ||
|
|
ddcabbf95d | ||
|
|
5c58b45340 | ||
|
|
5690b639cd | ||
|
|
22d0ff8173 | ||
|
|
8e61fe805c | ||
|
|
5356436c94 | ||
|
|
8ca8b92f92 | ||
|
|
7c98f20e11 | ||
|
|
1a8798a5d4 | ||
|
|
926842a216 | ||
|
|
9e11b860ec | ||
|
|
5abf1ee817 | ||
|
|
4e78e80619 | ||
|
|
33be9840f0 | ||
|
|
33fdaee399 | ||
|
|
91660a98d5 | ||
|
|
8d5c4492d8 | ||
|
|
29ad52bb46 | ||
|
|
590b7b7682 | ||
|
|
ea06d9614f | ||
|
|
41ba55634a | ||
|
|
f09616e780 | ||
|
|
a4d7e35cdd | ||
|
|
346e0ea734 | ||
|
|
ce854236cf | ||
|
|
1588663358 | ||
|
|
68fdc0a2b6 | ||
|
|
d335f8f981 | ||
|
|
b4efd15afd | ||
|
|
0e6f9da761 | ||
|
|
02b34f05c8 | ||
|
|
f26800eb85 | ||
|
|
d2cc01169b | ||
|
|
d8924cd8a9 | ||
|
|
25af5d2968 | ||
|
|
3580d4ff85 | ||
|
|
42607aadc3 | ||
|
|
2fe86a4cde | ||
|
|
db0256e0f7 | ||
|
|
224e808c21 | ||
|
|
6ed6b937f8 | ||
|
|
99b940f56f | ||
|
|
66a417a3ee | ||
|
|
761762bdc0 | ||
|
|
bd8ada9e4a | ||
|
|
ba5c7e4fc3 | ||
|
|
903284fe59 | ||
|
|
cf7ee0081c | ||
|
|
e6a1d8156b | ||
|
|
b850f51ef5 | ||
|
|
4e57de4776 | ||
|
|
0a2945e7f1 | ||
|
|
4f9beae49d | ||
|
|
15a49334d8 | ||
|
|
cf5e7c39e0 | ||
|
|
173ee5fcdc | ||
|
|
67968dff7e | ||
|
|
c596c70d42 | ||
|
|
d30c3aef32 | ||
|
|
f7e5101753 | ||
|
|
597b13d1b3 | ||
|
|
2b8385a874 | ||
|
|
ddcc301ab6 | ||
|
|
ab983c678f | ||
|
|
79dfbec910 | ||
|
|
6bf439d4ce | ||
|
|
063c5ee328 | ||
|
|
19137b8e8c | ||
|
|
95aa30ad41 | ||
|
|
55aa96a33a | ||
|
|
5460348450 | ||
|
|
0d1a68ecb4 | ||
|
|
98466220c7 | ||
|
|
6d2dd076dc | ||
|
|
5e07bcb79a | ||
|
|
9c81a74743 | ||
|
|
a3f2b1d829 | ||
|
|
cbdd71e44e | ||
|
|
f01cf5e04c | ||
|
|
bbb3063cb2 |
9
.github/aur/flux-bin/.SRCINFO.template
vendored
9
.github/aur/flux-bin/.SRCINFO.template
vendored
@@ -4,19 +4,16 @@ pkgbase = flux-bin
|
|||||||
pkgrel = ${PKGREL}
|
pkgrel = ${PKGREL}
|
||||||
url = https://fluxcd.io/
|
url = https://fluxcd.io/
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
arch = armv6h
|
|
||||||
arch = armv7h
|
arch = armv7h
|
||||||
arch = aarch64
|
arch = aarch64
|
||||||
license = APACHE
|
license = APACHE
|
||||||
optdepends = bash-completion: auto-completion for flux in Bash
|
optdepends = bash-completion: auto-completion for flux in Bash
|
||||||
optdepends = zsh-completions: auto-completion for flux in ZSH
|
optdepends = zsh-completions: auto-completion for flux in ZSH
|
||||||
source_x86_64 = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_amd64.tar.gz
|
source_x86_64 = flux-bin-${PKGVER}_linux_amd64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_amd64.tar.gz
|
||||||
sha256sums_x86_64 = ${SHA256SUM_AMD64}
|
sha256sums_x86_64 = ${SHA256SUM_AMD64}
|
||||||
source_armv6h = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm.tar.gz
|
source_armv7h = flux-bin-${PKGVER}_linux_arm.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm.tar.gz
|
||||||
sha256sums_armv6h = ${SHA256SUM_ARM}
|
|
||||||
source_armv7h = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm.tar.gz
|
|
||||||
sha256sums_armv7h = ${SHA256SUM_ARM}
|
sha256sums_armv7h = ${SHA256SUM_ARM}
|
||||||
source_aarch64 = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm64.tar.gz
|
source_aarch64 = flux-bin-${PKGVER}_linux_arm64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm64.tar.gz
|
||||||
sha256sums_aarch64 = ${SHA256SUM_ARM64}
|
sha256sums_aarch64 = ${SHA256SUM_ARM64}
|
||||||
|
|
||||||
pkgname = flux-bin
|
pkgname = flux-bin
|
||||||
|
|||||||
14
.github/aur/flux-bin/PKGBUILD.template
vendored
14
.github/aur/flux-bin/PKGBUILD.template
vendored
@@ -8,28 +8,22 @@ _srcname=flux
|
|||||||
_srcver=${VERSION}
|
_srcver=${VERSION}
|
||||||
pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
|
pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
|
||||||
url="https://fluxcd.io/"
|
url="https://fluxcd.io/"
|
||||||
arch=("x86_64" "armv6h" "armv7h" "aarch64")
|
arch=("x86_64" "armv7h" "aarch64")
|
||||||
license=("APACHE")
|
license=("APACHE")
|
||||||
optdepends=('bash-completion: auto-completion for flux in Bash'
|
optdepends=('bash-completion: auto-completion for flux in Bash'
|
||||||
'zsh-completions: auto-completion for flux in ZSH')
|
'zsh-completions: auto-completion for flux in ZSH')
|
||||||
source_x86_64=(
|
source_x86_64=(
|
||||||
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_amd64.tar.gz"
|
"${pkgname}-${pkgver}_linux_amd64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_amd64.tar.gz"
|
||||||
)
|
|
||||||
source_armv6h=(
|
|
||||||
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm.tar.gz"
|
|
||||||
)
|
)
|
||||||
source_armv7h=(
|
source_armv7h=(
|
||||||
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm.tar.gz"
|
"${pkgname}-${pkgver}_linux_arm.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm.tar.gz"
|
||||||
)
|
)
|
||||||
source_aarch64=(
|
source_aarch64=(
|
||||||
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm64.tar.gz"
|
"${pkgname}-${pkgver}_linux_arm64.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_arm64.tar.gz"
|
||||||
)
|
)
|
||||||
sha256sums_x86_64=(
|
sha256sums_x86_64=(
|
||||||
${SHA256SUM_AMD64}
|
${SHA256SUM_AMD64}
|
||||||
)
|
)
|
||||||
sha256sums_armv6h=(
|
|
||||||
${SHA256SUM_ARM}
|
|
||||||
)
|
|
||||||
sha256sums_armv7h=(
|
sha256sums_armv7h=(
|
||||||
${SHA256SUM_ARM}
|
${SHA256SUM_ARM}
|
||||||
)
|
)
|
||||||
|
|||||||
1
.github/aur/flux-go/.SRCINFO.template
vendored
1
.github/aur/flux-go/.SRCINFO.template
vendored
@@ -4,7 +4,6 @@ pkgbase = flux-go
|
|||||||
pkgrel = ${PKGREL}
|
pkgrel = ${PKGREL}
|
||||||
url = https://fluxcd.io/
|
url = https://fluxcd.io/
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
arch = armv6h
|
|
||||||
arch = armv7h
|
arch = armv7h
|
||||||
arch = aarch64
|
arch = aarch64
|
||||||
license = APACHE
|
license = APACHE
|
||||||
|
|||||||
6
.github/aur/flux-go/PKGBUILD.template
vendored
6
.github/aur/flux-go/PKGBUILD.template
vendored
@@ -8,13 +8,13 @@ _srcname=flux
|
|||||||
_srcver=${VERSION}
|
_srcver=${VERSION}
|
||||||
pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
|
pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
|
||||||
url="https://fluxcd.io/"
|
url="https://fluxcd.io/"
|
||||||
arch=("x86_64" "armv6h" "armv7h" "aarch64")
|
arch=("x86_64" "armv7h" "aarch64")
|
||||||
license=("APACHE")
|
license=("APACHE")
|
||||||
provides=("flux-bin")
|
provides=("flux-bin")
|
||||||
conflicts=("flux-bin")
|
conflicts=("flux-bin")
|
||||||
replaces=("flux-cli")
|
replaces=("flux-cli")
|
||||||
depends=("glibc")
|
depends=("glibc")
|
||||||
makedepends=('go>=1.17', 'kustomize>=3.0')
|
makedepends=('go>=1.20', 'kustomize>=5.0')
|
||||||
optdepends=('bash-completion: auto-completion for flux in Bash',
|
optdepends=('bash-completion: auto-completion for flux in Bash',
|
||||||
'zsh-completions: auto-completion for flux in ZSH')
|
'zsh-completions: auto-completion for flux in ZSH')
|
||||||
source=(
|
source=(
|
||||||
@@ -41,7 +41,7 @@ check() {
|
|||||||
aarch64)
|
aarch64)
|
||||||
export ENVTEST_ARCH=arm64
|
export ENVTEST_ARCH=arm64
|
||||||
;;
|
;;
|
||||||
armv6h|armv7h)
|
armv7h)
|
||||||
export ENVTEST_ARCH=arm
|
export ENVTEST_ARCH=arm
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
1
.github/aur/flux-scm/.SRCINFO.template
vendored
1
.github/aur/flux-scm/.SRCINFO.template
vendored
@@ -4,7 +4,6 @@ pkgbase = flux-scm
|
|||||||
pkgrel = ${PKGREL}
|
pkgrel = ${PKGREL}
|
||||||
url = https://fluxcd.io/
|
url = https://fluxcd.io/
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
arch = armv6h
|
|
||||||
arch = armv7h
|
arch = armv7h
|
||||||
arch = aarch64
|
arch = aarch64
|
||||||
license = APACHE
|
license = APACHE
|
||||||
|
|||||||
6
.github/aur/flux-scm/PKGBUILD.template
vendored
6
.github/aur/flux-scm/PKGBUILD.template
vendored
@@ -7,12 +7,12 @@ pkgrel=${PKGREL}
|
|||||||
_srcname=flux
|
_srcname=flux
|
||||||
pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
|
pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
|
||||||
url="https://fluxcd.io/"
|
url="https://fluxcd.io/"
|
||||||
arch=("x86_64" "armv6h" "armv7h" "aarch64")
|
arch=("x86_64" "armv7h" "aarch64")
|
||||||
license=("APACHE")
|
license=("APACHE")
|
||||||
provides=("flux-bin")
|
provides=("flux-bin")
|
||||||
conflicts=("flux-bin")
|
conflicts=("flux-bin")
|
||||||
depends=("glibc")
|
depends=("glibc")
|
||||||
makedepends=('go>=1.17', 'kustomize>=3.0', 'git')
|
makedepends=('go>=1.20', 'kustomize>=5.0', 'git')
|
||||||
optdepends=('bash-completion: auto-completion for flux in Bash',
|
optdepends=('bash-completion: auto-completion for flux in Bash',
|
||||||
'zsh-completions: auto-completion for flux in ZSH')
|
'zsh-completions: auto-completion for flux in ZSH')
|
||||||
source=(
|
source=(
|
||||||
@@ -42,7 +42,7 @@ check() {
|
|||||||
aarch64)
|
aarch64)
|
||||||
export ENVTEST_ARCH=arm64
|
export ENVTEST_ARCH=arm64
|
||||||
;;
|
;;
|
||||||
armv6h|armv7h)
|
armv7h)
|
||||||
export ENVTEST_ARCH=arm
|
export ENVTEST_ARCH=arm
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@@ -3,7 +3,14 @@ version: 2
|
|||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
labels: ["area/build"]
|
labels: ["area/ci", "dependencies"]
|
||||||
|
groups:
|
||||||
|
# Group all updates together, so that they are all applied in a single PR.
|
||||||
|
# Grouped updates are currently in beta and is subject to change.
|
||||||
|
# xref: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups
|
||||||
|
ci:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
schedule:
|
schedule:
|
||||||
# by default this will be on a monday.
|
# By default, this will be on a monday.
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
|||||||
52
.github/labels.yaml
vendored
Normal file
52
.github/labels.yaml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Configuration file to declaratively configure labels
|
||||||
|
# Ref: https://github.com/EndBug/label-sync#Config-files
|
||||||
|
|
||||||
|
- name: area/bootstrap
|
||||||
|
description: Bootstrap related issues and pull requests
|
||||||
|
color: '#86efc9'
|
||||||
|
- name: area/install
|
||||||
|
description: Install and uninstall related issues and pull requests
|
||||||
|
color: '#86efc9'
|
||||||
|
- name: area/diff
|
||||||
|
description: Diff related issues and pull requests
|
||||||
|
color: '#BA4192'
|
||||||
|
- name: area/bucket
|
||||||
|
description: Bucket related issues and pull requests
|
||||||
|
color: '#00b140'
|
||||||
|
- name: area/git
|
||||||
|
description: Git related issues and pull requests
|
||||||
|
color: '#863faf'
|
||||||
|
- name: area/oci
|
||||||
|
description: OCI related issues and pull requests
|
||||||
|
color: '#c739ff'
|
||||||
|
- name: area/kustomization
|
||||||
|
description: Kustomization related issues and pull requests
|
||||||
|
color: '#00e54d'
|
||||||
|
- name: area/helm
|
||||||
|
description: Helm related issues and pull requests
|
||||||
|
color: '#1673b6'
|
||||||
|
- name: area/image-automation
|
||||||
|
description: Automated image updates related issues and pull requests
|
||||||
|
color: '#c5def5'
|
||||||
|
- name: area/monitoring
|
||||||
|
description: Monitoring related issues and pull requests
|
||||||
|
color: '#dd75ae'
|
||||||
|
- name: area/multi-tenancy
|
||||||
|
description: Multi-tenancy related issues and pull requests
|
||||||
|
color: '#72CDBD'
|
||||||
|
- name: area/notification
|
||||||
|
description: Notification API related issues and pull requests
|
||||||
|
color: '#434ec1'
|
||||||
|
- name: area/source
|
||||||
|
description: Source API related issues and pull requests
|
||||||
|
color: '#863faf'
|
||||||
|
- name: area/rfc
|
||||||
|
description: Feature request proposals in the RFC format
|
||||||
|
color: '#D621C3'
|
||||||
|
aliases: ['area/RFC']
|
||||||
|
- name: backport:release/v2.0.x
|
||||||
|
description: To be backported to release/v2.0.x
|
||||||
|
color: '#ffd700'
|
||||||
|
- name: backport:release/v2.1.x
|
||||||
|
description: To be backported to release/v2.1.x
|
||||||
|
color: '#ffd700'
|
||||||
29
.github/workflows/action.yaml
vendored
Normal file
29
.github/workflows/action.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: test-gh-action
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'action/**'
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'action/**'
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
- 'release/**'
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
actions:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
version: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.version }}
|
||||||
|
name: action on ${{ matrix.version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
- name: Setup flux
|
||||||
|
uses: ./action
|
||||||
31
.github/workflows/backport.yaml
vendored
Normal file
31
.github/workflows/backport.yaml
vendored
Normal file
@@ -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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
- name: Create backport PRs
|
||||||
|
uses: korthout/backport-action@08bafb375e6e9a9a2b53a744b987e5d81a133191 # v2.1.1
|
||||||
|
# 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}.
|
||||||
9
.github/workflows/e2e-arm64.yaml
vendored
9
.github/workflows/e2e-arm64.yaml
vendored
@@ -3,7 +3,7 @@ name: e2e-arm64
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ main, update-components, e2e-*, release-* ]
|
branches: [ 'main', 'update-components', 'e2e-*', 'release/**' ]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -17,12 +17,13 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
# Keep this list up-to-date with https://endoflife.date/kubernetes
|
# Keep this list up-to-date with https://endoflife.date/kubernetes
|
||||||
# Check which versions are available on DockerHub with 'crane ls kindest/node'
|
# Check which versions are available on DockerHub with 'crane ls kindest/node'
|
||||||
KUBERNETES_VERSION: [ 1.25.8, 1.26.3, 1.27.1 ]
|
KUBERNETES_VERSION: [ 1.25.11, 1.26.6, 1.27.3, 1.28.0 ]
|
||||||
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
|
|||||||
85
.github/workflows/e2e-azure.yaml
vendored
85
.github/workflows/e2e-azure.yaml
vendored
@@ -3,7 +3,7 @@ name: e2e-azure
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 6 * * *'
|
- cron: '0 6 * * *'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -23,30 +23,32 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
e2e-amd64-aks:
|
e2e-amd64-aks:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./tests/azure
|
||||||
|
# This job is currently disabled. Remove the false check when Azure subscription is enabled.
|
||||||
|
if: false && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: tests/azure/go.sum
|
||||||
**/go.sum
|
|
||||||
**/go.mod
|
|
||||||
- name: Setup Flux CLI
|
- name: Setup Flux CLI
|
||||||
run: |
|
run: |
|
||||||
make build
|
make build
|
||||||
mkdir -p $HOME/.local/bin
|
mkdir -p $HOME/.local/bin
|
||||||
mv ./bin/flux $HOME/.local/bin
|
mv ./bin/flux $HOME/.local/bin
|
||||||
|
working-directory: ./
|
||||||
- name: Setup SOPS
|
- name: Setup SOPS
|
||||||
run: |
|
run: |
|
||||||
wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux
|
|
||||||
chmod +x sops-v3.7.1.linux
|
|
||||||
mkdir -p $HOME/.local/bin
|
mkdir -p $HOME/.local/bin
|
||||||
mv sops-v3.7.1.linux $HOME/.local/bin/sops
|
wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux -O $HOME/.local/bin/sops
|
||||||
|
chmod +x $HOME/.local/bin/sops
|
||||||
- name: Setup Terraform
|
- name: Setup Terraform
|
||||||
uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2
|
uses: hashicorp/setup-terraform@a1502cd9e758c50496cc9ac5308c4843bcd56d36 # v2
|
||||||
with:
|
with:
|
||||||
terraform_version: 1.2.8
|
terraform_version: 1.2.8
|
||||||
terraform_wrapper: false
|
terraform_wrapper: false
|
||||||
@@ -60,9 +62,64 @@ jobs:
|
|||||||
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
|
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
|
||||||
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
|
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
|
||||||
run: |
|
run: |
|
||||||
echo $HOME
|
|
||||||
echo $PATH
|
|
||||||
ls $HOME/.local/bin
|
ls $HOME/.local/bin
|
||||||
az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID}
|
az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID}
|
||||||
cd ./tests/azure
|
|
||||||
go test -v -coverprofile cover.out -timeout 60m .
|
go test -v -coverprofile cover.out -timeout 60m .
|
||||||
|
|
||||||
|
refactored-e2e-amd64-aks:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./tests/integration
|
||||||
|
# This job is currently disabled. Remove the false check when Azure subscription is enabled.
|
||||||
|
if: false && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||||
|
steps:
|
||||||
|
- name: CheckoutD
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
|
with:
|
||||||
|
go-version: 1.20.x
|
||||||
|
cache-dependency-path: tests/integration/go.sum
|
||||||
|
- name: Setup Flux CLI
|
||||||
|
run: make build
|
||||||
|
working-directory: ./
|
||||||
|
- name: Setup SOPS
|
||||||
|
run: |
|
||||||
|
mkdir -p $HOME/.local/bin
|
||||||
|
wget -O $HOME/.local/bin/sops https://github.com/mozilla/sops/releases/download/v$SOPS_VER/sops-v$SOPS_VER.linux
|
||||||
|
chmod +x $HOME/.local/bin/sops
|
||||||
|
env:
|
||||||
|
SOPS_VER: 3.7.1
|
||||||
|
- name: Authenticate to Azure
|
||||||
|
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
||||||
|
with:
|
||||||
|
creds: '{"clientId":"${{ secrets.AZ_ARM_CLIENT_ID }}","clientSecret":"${{ secrets.AZ_ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZ_ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZ_ARM_TENANT_ID }}"}'
|
||||||
|
- name: Set dynamic variables in .env
|
||||||
|
run: |
|
||||||
|
cat > .env <<EOF
|
||||||
|
export TF_VAR_tags='{ "environment"="github", "ci"="true", "repo"="flux2", "createdat"="$(date -u +x%Y-%m-%d_%Hh%Mm%Ss)" }'
|
||||||
|
EOF
|
||||||
|
- name: Print .env for dynamic tag value reference
|
||||||
|
run: cat .env
|
||||||
|
- name: Run Azure e2e tests
|
||||||
|
env:
|
||||||
|
ARM_CLIENT_ID: ${{ secrets.AZ_ARM_CLIENT_ID }}
|
||||||
|
ARM_CLIENT_SECRET: ${{ secrets.AZ_ARM_CLIENT_SECRET }}
|
||||||
|
ARM_SUBSCRIPTION_ID: ${{ secrets.AZ_ARM_SUBSCRIPTION_ID }}
|
||||||
|
ARM_TENANT_ID: ${{ secrets.AZ_ARM_TENANT_ID }}
|
||||||
|
TF_VAR_azuredevops_org: ${{ secrets.TF_VAR_azuredevops_org }}
|
||||||
|
TF_VAR_azuredevops_pat: ${{ secrets.TF_VAR_azuredevops_pat }}
|
||||||
|
TF_VAR_location: ${{ vars.TF_VAR_azure_location }}
|
||||||
|
GITREPO_SSH_CONTENTS: ${{ secrets.AZURE_GITREPO_SSH_CONTENTS }}
|
||||||
|
GITREPO_SSH_PUB_CONTENTS: ${{ secrets.AZURE_GITREPO_SSH_PUB_CONTENTS }}
|
||||||
|
run: |
|
||||||
|
source .env
|
||||||
|
mkdir -p ./build/ssh
|
||||||
|
touch ./build/ssh/key
|
||||||
|
echo $GITREPO_SSH_CONTENTS | base64 -d > build/ssh/key
|
||||||
|
export GITREPO_SSH_PATH=build/ssh/key
|
||||||
|
touch ./build/ssh/key.pub
|
||||||
|
echo $GITREPO_SSH_PUB_CONTENTS | base64 -d > ./build/ssh/key.pub
|
||||||
|
export GITREPO_SSH_PUB_PATH=build/ssh/key.pub
|
||||||
|
make test-azure
|
||||||
|
|||||||
18
.github/workflows/e2e-bootstrap.yaml
vendored
18
.github/workflows/e2e-bootstrap.yaml
vendored
@@ -3,9 +3,10 @@ name: e2e-bootstrap
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ main, release-* ]
|
branches: [ 'main', 'release/**' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main, release-* ]
|
branches: [ 'main', 'release/**' ]
|
||||||
|
paths-ignore: [ 'docs/**', 'rfcs/**' ]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -16,23 +17,23 @@ jobs:
|
|||||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
**/go.sum
|
**/go.sum
|
||||||
**/go.mod
|
**/go.mod
|
||||||
- name: Setup Kubernetes
|
- name: Setup Kubernetes
|
||||||
uses: helm/kind-action@fa81e57adff234b2908110485695db0f181f3c67 # v1.7.0
|
uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0
|
||||||
with:
|
with:
|
||||||
version: v0.17.0
|
version: v0.20.0
|
||||||
cluster_name: kind
|
cluster_name: kind
|
||||||
# The versions below should target the newest Kubernetes version
|
# The versions below should target the newest Kubernetes version
|
||||||
# Keep this up-to-date with https://endoflife.date/kubernetes
|
# Keep this up-to-date with https://endoflife.date/kubernetes
|
||||||
node_image: kindest/node:v1.26.0
|
node_image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
|
||||||
kubectl_version: v1.26.2
|
kubectl_version: v1.28.0
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg/actions/kustomize@main
|
uses: fluxcd/pkg/actions/kustomize@main
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -98,7 +99,6 @@ jobs:
|
|||||||
--path=test-cluster \
|
--path=test-cluster \
|
||||||
--read-write-key
|
--read-write-key
|
||||||
/tmp/flux reconcile image repository podinfo
|
/tmp/flux reconcile image repository podinfo
|
||||||
/tmp/flux reconcile image update flux-system
|
|
||||||
/tmp/flux get images all
|
/tmp/flux get images all
|
||||||
|
|
||||||
retries=10
|
retries=10
|
||||||
|
|||||||
92
.github/workflows/e2e-gcp.yaml
vendored
Normal file
92
.github/workflows/e2e-gcp.yaml
vendored
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
name: e2e-gcp
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 6 * * *'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'tests/**'
|
||||||
|
- '.github/workflows/e2e-gcp.yaml'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'tests/**'
|
||||||
|
- '.github/workflows/e2e-gcp.yaml'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e-gcp:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./tests/integration
|
||||||
|
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
|
with:
|
||||||
|
go-version: 1.20.x
|
||||||
|
cache-dependency-path: tests/integration/go.sum
|
||||||
|
- name: Setup Flux CLI
|
||||||
|
run: make build
|
||||||
|
working-directory: ./
|
||||||
|
- name: Setup SOPS
|
||||||
|
run: |
|
||||||
|
mkdir -p $HOME/.local/bin
|
||||||
|
wget -O $HOME/.local/bin/sops https://github.com/mozilla/sops/releases/download/v$SOPS_VER/sops-v$SOPS_VER.linux
|
||||||
|
chmod +x $HOME/.local/bin/sops
|
||||||
|
env:
|
||||||
|
SOPS_VER: 3.7.1
|
||||||
|
- name: Authenticate to Google Cloud
|
||||||
|
uses: google-github-actions/auth@35b0e87d162680511bf346c299f71c9c5c379033 # v1.1.1
|
||||||
|
id: 'auth'
|
||||||
|
with:
|
||||||
|
credentials_json: '${{ secrets.FLUX2_E2E_GOOGLE_CREDENTIALS }}'
|
||||||
|
token_format: 'access_token'
|
||||||
|
- name: Setup gcloud
|
||||||
|
uses: google-github-actions/setup-gcloud@e30db14379863a8c79331b04a9969f4c1e225e0b # v1.1.1
|
||||||
|
- name: Setup QEMU
|
||||||
|
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
||||||
|
- name: Log into us-central1-docker.pkg.dev
|
||||||
|
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||||
|
with:
|
||||||
|
registry: us-central1-docker.pkg.dev
|
||||||
|
username: oauth2accesstoken
|
||||||
|
password: ${{ steps.auth.outputs.access_token }}
|
||||||
|
- name: Set dynamic variables in .env
|
||||||
|
run: |
|
||||||
|
cat > .env <<EOF
|
||||||
|
export TF_VAR_tags='{ "environment"="github", "ci"="true", "repo"="flux2", "createdat"="$(date -u +x%Y-%m-%d_%Hh%Mm%Ss)" }'
|
||||||
|
EOF
|
||||||
|
- name: Print .env for dynamic tag value reference
|
||||||
|
run: cat .env
|
||||||
|
- name: Run GCP e2e tests
|
||||||
|
env:
|
||||||
|
TF_VAR_gcp_project_id: ${{ vars.TF_VAR_gcp_project_id }}
|
||||||
|
TF_VAR_gcp_region: ${{ vars.TF_VAR_gcp_region }}
|
||||||
|
TF_VAR_gcp_zone: ${{ vars.TF_VAR_gcp_zone }}
|
||||||
|
TF_VAR_gcp_email: ${{ secrets.TF_VAR_gcp_email }}
|
||||||
|
TF_VAR_gcp_keyring: ${{ secrets.TF_VAR_gcp_keyring }}
|
||||||
|
TF_VAR_gcp_crypto_key: ${{ secrets.TF_VAR_gcp_crypto_key }}
|
||||||
|
GITREPO_SSH_CONTENTS: ${{ secrets.GCP_GITREPO_SSH_CONTENTS }}
|
||||||
|
GITREPO_SSH_PUB_CONTENTS: ${{ secrets.GCP_GITREPO_SSH_PUB_CONTENTS }}
|
||||||
|
run: |
|
||||||
|
source .env
|
||||||
|
mkdir -p ./build/ssh
|
||||||
|
touch ./build/ssh/key
|
||||||
|
echo $GITREPO_SSH_CONTENTS | base64 -d > build/ssh/key
|
||||||
|
export GITREPO_SSH_PATH=build/ssh/key
|
||||||
|
touch ./build/ssh/key.pub
|
||||||
|
echo $GITREPO_SSH_PUB_CONTENTS | base64 -d > ./build/ssh/key.pub
|
||||||
|
export GITREPO_SSH_PUB_PATH=build/ssh/key.pub
|
||||||
|
make test-gcp
|
||||||
17
.github/workflows/e2e.yaml
vendored
17
.github/workflows/e2e.yaml
vendored
@@ -3,9 +3,10 @@ name: e2e
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ main, release-* ]
|
branches: [ 'main', 'release/**' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main, release-* ]
|
branches: [ 'main', 'release/**' ]
|
||||||
|
paths-ignore: [ 'docs/**', 'rfcs/**' ]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -20,24 +21,24 @@ jobs:
|
|||||||
- 5000:5000
|
- 5000:5000
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
**/go.sum
|
**/go.sum
|
||||||
**/go.mod
|
**/go.mod
|
||||||
- name: Setup Kubernetes
|
- name: Setup Kubernetes
|
||||||
uses: helm/kind-action@fa81e57adff234b2908110485695db0f181f3c67 # v1.7.0
|
uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0
|
||||||
with:
|
with:
|
||||||
version: v0.17.0
|
version: v0.20.0
|
||||||
cluster_name: kind
|
cluster_name: kind
|
||||||
config: .github/kind/config.yaml # disable KIND-net
|
config: .github/kind/config.yaml # disable KIND-net
|
||||||
# The versions below should target the newest Kubernetes version
|
# The versions below should target the newest Kubernetes version
|
||||||
# Keep this up-to-date with https://endoflife.date/kubernetes
|
# Keep this up-to-date with https://endoflife.date/kubernetes
|
||||||
node_image: kindest/node:v1.26.0
|
node_image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
|
||||||
kubectl_version: v1.26.2
|
kubectl_version: v1.28.0
|
||||||
- name: Setup Calico for network policy
|
- name: Setup Calico for network policy
|
||||||
run: |
|
run: |
|
||||||
kubectl apply -f https://docs.projectcalico.org/v3.25/manifests/calico.yaml
|
kubectl apply -f https://docs.projectcalico.org/v3.25/manifests/calico.yaml
|
||||||
|
|||||||
8
.github/workflows/ossf.yaml
vendored
8
.github/workflows/ossf.yaml
vendored
@@ -19,21 +19,21 @@ jobs:
|
|||||||
actions: read
|
actions: read
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Run analysis
|
- name: Run analysis
|
||||||
uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # v2.1.3
|
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||||
with:
|
with:
|
||||||
results_file: results.sarif
|
results_file: results.sarif
|
||||||
results_format: sarif
|
results_format: sarif
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
publish_results: true
|
publish_results: true
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||||
with:
|
with:
|
||||||
name: SARIF file
|
name: SARIF file
|
||||||
path: results.sarif
|
path: results.sarif
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
- name: Upload SARIF results
|
- name: Upload SARIF results
|
||||||
uses: github/codeql-action/upload-sarif@0225834cc549ee0ca93cb085b92954821a145866 # v2.3.5
|
uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
87
.github/workflows/release.yaml
vendored
87
.github/workflows/release.yaml
vendored
@@ -9,6 +9,10 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-flux-cli:
|
release-flux-cli:
|
||||||
|
outputs:
|
||||||
|
hashes: ${{ steps.slsa.outputs.hashes }}
|
||||||
|
image_url: ${{ steps.slsa.outputs.image_url }}
|
||||||
|
image_digest: ${{ steps.slsa.outputs.image_digest }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write # needed to write releases
|
contents: write # needed to write releases
|
||||||
@@ -16,33 +20,33 @@ jobs:
|
|||||||
packages: write # needed for ghcr access
|
packages: write # needed for ghcr access
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Unshallow
|
- name: Unshallow
|
||||||
run: git fetch --prune --unshallow
|
run: git fetch --prune --unshallow
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache: false
|
cache: false
|
||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
|
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2.5.0
|
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
||||||
- name: Setup Syft
|
- name: Setup Syft
|
||||||
uses: anchore/sbom-action/download-syft@4d571ad1038a9cc29d676154ef265ab8f9027042 # v0.14.2
|
uses: anchore/sbom-action/download-syft@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1 # v0.14.3
|
||||||
- name: Setup Cosign
|
- name: Setup Cosign
|
||||||
uses: sigstore/cosign-installer@dd6b2e2b610a11fd73dd187a43d57cc1394e35f9 # v3.0.5
|
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg/actions/kustomize@main
|
uses: fluxcd/pkg/actions/kustomize@main
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
|
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.GHCR_TOKEN }}
|
password: ${{ secrets.GHCR_TOKEN }}
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
|
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||||
with:
|
with:
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||||
@@ -74,7 +78,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4.2.0
|
id: run-goreleaser
|
||||||
|
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --release-notes=output/notes.md --skip-validate
|
args: release --release-notes=output/notes.md --skip-validate
|
||||||
@@ -82,6 +87,22 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||||
AUR_BOT_SSH_PRIVATE_KEY: ${{ secrets.AUR_BOT_SSH_PRIVATE_KEY }}
|
AUR_BOT_SSH_PRIVATE_KEY: ${{ secrets.AUR_BOT_SSH_PRIVATE_KEY }}
|
||||||
|
- name: Generate SLSA metadata
|
||||||
|
id: slsa
|
||||||
|
env:
|
||||||
|
ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
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/flux-cli:$GITHUB_REF_NAME
|
||||||
|
echo "image_url=$image_url" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
image_digest=$(docker buildx imagetools inspect ${image_url} --format '{{json .}}' | jq -r .manifest.digest)
|
||||||
|
echo "image_digest=$image_digest" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
release-flux-manifests:
|
release-flux-manifests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: release-flux-cli
|
needs: release-flux-cli
|
||||||
@@ -89,7 +110,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg/actions/kustomize@main
|
uses: fluxcd/pkg/actions/kustomize@main
|
||||||
- name: Setup Flux CLI
|
- name: Setup Flux CLI
|
||||||
@@ -100,13 +121,13 @@ jobs:
|
|||||||
VERSION=$(flux version --client | awk '{ print $NF }')
|
VERSION=$(flux version --client | awk '{ print $NF }')
|
||||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
|
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.GHCR_TOKEN }}
|
password: ${{ secrets.GHCR_TOKEN }}
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
|
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||||
with:
|
with:
|
||||||
username: fluxcdbot
|
username: fluxcdbot
|
||||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||||
@@ -134,7 +155,7 @@ jobs:
|
|||||||
--path="./flux-system" \
|
--path="./flux-system" \
|
||||||
--source=${{ github.repositoryUrl }} \
|
--source=${{ github.repositoryUrl }} \
|
||||||
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
|
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
|
||||||
- uses: sigstore/cosign-installer@dd6b2e2b610a11fd73dd187a43d57cc1394e35f9 # v3.0.5
|
- uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
|
||||||
- name: Sign manifests
|
- name: Sign manifests
|
||||||
env:
|
env:
|
||||||
COSIGN_EXPERIMENTAL: 1
|
COSIGN_EXPERIMENTAL: 1
|
||||||
@@ -148,3 +169,43 @@ jobs:
|
|||||||
|
|
||||||
flux tag artifact oci://docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \
|
flux tag artifact oci://docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \
|
||||||
--tag latest
|
--tag latest
|
||||||
|
|
||||||
|
release-provenance:
|
||||||
|
needs: [release-flux-cli]
|
||||||
|
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.
|
||||||
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
|
||||||
|
with:
|
||||||
|
provenance-name: "provenance.intoto.jsonl"
|
||||||
|
base64-subjects: "${{ needs.release-flux-cli.outputs.hashes }}"
|
||||||
|
upload-assets: true
|
||||||
|
|
||||||
|
dockerhub-provenance:
|
||||||
|
needs: [release-flux-cli]
|
||||||
|
permissions:
|
||||||
|
actions: read # for detecting the Github Actions environment.
|
||||||
|
id-token: write # for creating OIDC tokens for signing.
|
||||||
|
packages: write # for uploading attestations.
|
||||||
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
|
||||||
|
with:
|
||||||
|
image: ${{ needs.release-flux-cli.outputs.image_url }}
|
||||||
|
digest: ${{ needs.release-flux-cli.outputs.image_digest }}
|
||||||
|
registry-username: fluxcdbot
|
||||||
|
secrets:
|
||||||
|
registry-password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||||
|
|
||||||
|
ghcr-provenance:
|
||||||
|
needs: [release-flux-cli]
|
||||||
|
permissions:
|
||||||
|
actions: read # for detecting the Github Actions environment.
|
||||||
|
id-token: write # for creating OIDC tokens for signing.
|
||||||
|
packages: write # for uploading attestations.
|
||||||
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
|
||||||
|
with:
|
||||||
|
image: ghcr.io/${{ needs.release-flux-cli.outputs.image_url }}
|
||||||
|
digest: ${{ needs.release-flux-cli.outputs.image_digest }}
|
||||||
|
registry-username: fluxcdbot
|
||||||
|
secrets:
|
||||||
|
registry-password: ${{ secrets.GHCR_TOKEN }}
|
||||||
|
|||||||
25
.github/workflows/scan.yaml
vendored
25
.github/workflows/scan.yaml
vendored
@@ -3,9 +3,9 @@ name: scan
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ 'main', 'release/**' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ 'main', 'release/**' ]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '18 10 * * 3'
|
- cron: '18 10 * * 3'
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.actor != 'dependabot[bot]'
|
if: github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Run FOSSA scan and upload build data
|
- name: Run FOSSA scan and upload build data
|
||||||
uses: fossa-contrib/fossa-action@6728dc6fe9a068c648d080c33829ffbe56565023 # v2.0.0
|
uses: fossa-contrib/fossa-action@6728dc6fe9a068c648d080c33829ffbe56565023 # v2.0.0
|
||||||
with:
|
with:
|
||||||
@@ -31,11 +31,11 @@ jobs:
|
|||||||
security-events: write
|
security-events: write
|
||||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Setup Kustomize
|
- name: Setup Kustomize
|
||||||
uses: fluxcd/pkg/actions/kustomize@main
|
uses: fluxcd/pkg/actions/kustomize@main
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||||
- name: Upload result to GitHub Code Scanning
|
- name: Upload result to GitHub Code Scanning
|
||||||
uses: github/codeql-action/upload-sarif@0225834cc549ee0ca93cb085b92954821a145866 # v2.3.5
|
uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||||
with:
|
with:
|
||||||
sarif_file: snyk.sarif
|
sarif_file: snyk.sarif
|
||||||
|
|
||||||
@@ -64,19 +64,22 @@ jobs:
|
|||||||
if: github.actor != 'dependabot[bot]'
|
if: github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
**/go.sum
|
**/go.sum
|
||||||
**/go.mod
|
**/go.mod
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@0225834cc549ee0ca93cb085b92954821a145866 # v2.3.5
|
uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||||
with:
|
with:
|
||||||
languages: go
|
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
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@0225834cc549ee0ca93cb085b92954821a145866 # v2.3.5
|
uses: github/codeql-action/autobuild@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@0225834cc549ee0ca93cb085b92954821a145866 # v2.3.5
|
uses: github/codeql-action/analyze@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||||
|
|||||||
28
.github/workflows/sync-labels.yaml
vendored
Normal file
28
.github/workflows/sync-labels.yaml
vendored
Normal file
@@ -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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
- uses: EndBug/label-sync@da00f2c11fdb78e4fae44adac2fdd713778ea3e8 # v2.3.2
|
||||||
|
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
|
||||||
8
.github/workflows/update.yaml
vendored
8
.github/workflows/update.yaml
vendored
@@ -18,9 +18,9 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
|
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.20.x
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 # v5.0.1
|
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||||
commit-message: |
|
commit-message: |
|
||||||
@@ -99,7 +99,7 @@ jobs:
|
|||||||
body: |
|
body: |
|
||||||
${{ steps.update.outputs.pr_body }}
|
${{ steps.update.outputs.pr_body }}
|
||||||
labels: |
|
labels: |
|
||||||
area/build
|
dependencies
|
||||||
reviewers: ${{ secrets.ASSIGNEES }}
|
reviewers: ${{ secrets.ASSIGNEES }}
|
||||||
|
|
||||||
- name: Check output
|
- name: Check output
|
||||||
|
|||||||
@@ -83,14 +83,7 @@ brews:
|
|||||||
install: |
|
install: |
|
||||||
bin.install "flux"
|
bin.install "flux"
|
||||||
|
|
||||||
bash_output = Utils.safe_popen_read(bin/"flux", "completion", "bash")
|
generate_completions_from_executable(bin/"flux", "completion")
|
||||||
(bash_completion/"flux").write bash_output
|
|
||||||
|
|
||||||
zsh_output = Utils.safe_popen_read(bin/"flux", "completion", "zsh")
|
|
||||||
(zsh_completion/"_flux").write zsh_output
|
|
||||||
|
|
||||||
fish_output = Utils.safe_popen_read(bin/"flux", "completion", "fish")
|
|
||||||
(fish_completion/"flux.fish").write fish_output
|
|
||||||
test: |
|
test: |
|
||||||
system "#{bin}/flux --version"
|
system "#{bin}/flux --version"
|
||||||
publishers:
|
publishers:
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ for source changes.
|
|||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
* go >= 1.20
|
* go >= 1.20
|
||||||
* kubectl >= 1.20
|
* kubectl >= 1.24
|
||||||
* kustomize >= 4.4
|
* kustomize >= 5.0
|
||||||
* coreutils (on Mac OS)
|
* coreutils (on Mac OS)
|
||||||
|
|
||||||
Install the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with:
|
Install the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM alpine:3.18 as builder
|
|||||||
RUN apk add --no-cache ca-certificates curl
|
RUN apk add --no-cache ca-certificates curl
|
||||||
|
|
||||||
ARG ARCH=linux/amd64
|
ARG ARCH=linux/amd64
|
||||||
ARG KUBECTL_VER=1.27.2
|
ARG KUBECTL_VER=1.27.3
|
||||||
|
|
||||||
RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
|
RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
|
||||||
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \
|
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \
|
||||||
|
|||||||
1
Makefile
1
Makefile
@@ -19,6 +19,7 @@ all: test build
|
|||||||
tidy:
|
tidy:
|
||||||
go mod tidy -compat=1.20
|
go mod tidy -compat=1.20
|
||||||
cd tests/azure && go mod tidy -compat=1.20
|
cd tests/azure && go mod tidy -compat=1.20
|
||||||
|
cd tests/integration && go mod tidy -compat=1.20
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -5,6 +5,7 @@
|
|||||||
[](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2)
|
[](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2)
|
||||||
[](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2?ref=badge_shield)
|
[](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2?ref=badge_shield)
|
||||||
[](https://artifacthub.io/packages/helm/fluxcd-community/flux2)
|
[](https://artifacthub.io/packages/helm/fluxcd-community/flux2)
|
||||||
|
[](https://fluxcd.io/flux/security/slsa-assessment)
|
||||||
|
|
||||||
Flux is a tool for keeping Kubernetes clusters in sync with sources of
|
Flux is a tool for keeping Kubernetes clusters in sync with sources of
|
||||||
configuration (like Git repositories and OCI artifacts),
|
configuration (like Git repositories and OCI artifacts),
|
||||||
@@ -32,7 +33,7 @@ For more comprehensive documentation, see the following guides:
|
|||||||
- [Ways of structuring your repositories](https://fluxcd.io/flux/guides/repository-structure/)
|
- [Ways of structuring your repositories](https://fluxcd.io/flux/guides/repository-structure/)
|
||||||
- [Manage Helm Releases](https://fluxcd.io/flux/guides/helmreleases/)
|
- [Manage Helm Releases](https://fluxcd.io/flux/guides/helmreleases/)
|
||||||
- [Automate image updates to Git](https://fluxcd.io/flux/guides/image-update/)
|
- [Automate image updates to Git](https://fluxcd.io/flux/guides/image-update/)
|
||||||
- [Manage Kubernetes secrets with Mozilla SOPS](https://fluxcd.io/flux/guides/mozilla-sops/)
|
- [Manage Kubernetes secrets with Flux and SOPS](https://fluxcd.io/flux/guides/mozilla-sops/)
|
||||||
|
|
||||||
If you need help, please refer to our **[Support page](https://fluxcd.io/support/)**.
|
If you need help, please refer to our **[Support page](https://fluxcd.io/support/)**.
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ runtime for Flux v2. The APIs comprise Kubernetes custom resources,
|
|||||||
which can be created and updated by a cluster user, or by other
|
which can be created and updated by a cluster user, or by other
|
||||||
automation tooling.
|
automation tooling.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
You can use the toolkit to extend Flux, or to build your own systems
|
You can use the toolkit to extend Flux, or to build your own systems
|
||||||
for continuous delivery -- see [the developer
|
for continuous delivery -- see [the developer
|
||||||
@@ -58,18 +59,18 @@ guides](https://fluxcd.io/flux/gitops-toolkit/source-watcher/).
|
|||||||
- [HelmChart CRD](https://fluxcd.io/flux/components/source/helmcharts/)
|
- [HelmChart CRD](https://fluxcd.io/flux/components/source/helmcharts/)
|
||||||
- [Bucket CRD](https://fluxcd.io/flux/components/source/buckets/)
|
- [Bucket CRD](https://fluxcd.io/flux/components/source/buckets/)
|
||||||
- [Kustomize Controller](https://fluxcd.io/flux/components/kustomize/)
|
- [Kustomize Controller](https://fluxcd.io/flux/components/kustomize/)
|
||||||
- [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomization/)
|
- [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomizations/)
|
||||||
- [Helm Controller](https://fluxcd.io/flux/components/helm/)
|
- [Helm Controller](https://fluxcd.io/flux/components/helm/)
|
||||||
- [HelmRelease CRD](https://fluxcd.io/flux/components/helm/helmreleases/)
|
- [HelmRelease CRD](https://fluxcd.io/flux/components/helm/helmreleases/)
|
||||||
- [Notification Controller](https://fluxcd.io/flux/components/notification/)
|
- [Notification Controller](https://fluxcd.io/flux/components/notification/)
|
||||||
- [Provider CRD](https://fluxcd.io/flux/components/notification/provider/)
|
- [Provider CRD](https://fluxcd.io/flux/components/notification/providers/)
|
||||||
- [Alert CRD](https://fluxcd.io/flux/components/notification/alert/)
|
- [Alert CRD](https://fluxcd.io/flux/components/notification/alerts/)
|
||||||
- [Receiver CRD](https://fluxcd.io/flux/components/notification/receiver/)
|
- [Receiver CRD](https://fluxcd.io/flux/components/notification/receivers/)
|
||||||
- [Image Automation Controllers](https://fluxcd.io/flux/components/image/)
|
- [Image Automation Controllers](https://fluxcd.io/flux/components/image/)
|
||||||
- [ImageRepository CRD](https://fluxcd.io/flux/components/image/imagerepositories/)
|
- [ImageRepository CRD](https://fluxcd.io/flux/components/image/imagerepositories/)
|
||||||
- [ImagePolicy CRD](https://fluxcd.io/flux/components/image/imagepolicies/)
|
- [ImagePolicy CRD](https://fluxcd.io/flux/components/image/imagepolicies/)
|
||||||
- [ImageUpdateAutomation CRD](https://fluxcd.io/flux/components/image/imageupdateautomations/)
|
- [ImageUpdateAutomation CRD](https://fluxcd.io/flux/components/image/imageupdateautomations/)
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
Need help or want to contribute? Please see the links below. The Flux project is always looking for
|
Need help or want to contribute? Please see the links below. The Flux project is always looking for
|
||||||
|
|||||||
220
action/README.md
220
action/README.md
@@ -1,216 +1,22 @@
|
|||||||
# Flux GitHub Action
|
# Flux GitHub Action
|
||||||
|
|
||||||
Usage:
|
To install the latest Flux CLI on Linux, macOS or Windows GitHub runners:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Flux CLI
|
- name: Setup Flux CLI
|
||||||
uses: fluxcd/flux2/action@main
|
uses: fluxcd/flux2/action@main
|
||||||
- name: Run Flux commands
|
with:
|
||||||
run: flux -v
|
version: 'latest'
|
||||||
|
- name: Run Flux CLI
|
||||||
|
run: flux version --client
|
||||||
```
|
```
|
||||||
|
|
||||||
The latest stable version of the `flux` binary is downloaded from
|
The Flux GitHub Action can be used to automate various tasks in CI, such as:
|
||||||
GitHub [releases](https://github.com/fluxcd/flux2/releases)
|
|
||||||
and placed at `/usr/local/bin/flux`.
|
|
||||||
|
|
||||||
Note that this action can only be used on GitHub **Linux** runners.
|
- [Automate Flux upgrades on clusters via Pull Requests](https://fluxcd.io/flux/flux-gh-action/#automate-flux-updates)
|
||||||
You can change the arch (defaults to `amd64`) with:
|
- [Push Kubernetes manifests to container registries](https://fluxcd.io/flux/flux-gh-action/#push-kubernetes-manifests-to-container-registries)
|
||||||
|
- [Run end-to-end testing with Flux and Kubernetes Kind](https://fluxcd.io/flux/flux-gh-action/#end-to-end-testing)
|
||||||
|
|
||||||
```yaml
|
For more information, please see the [Flux GitHub Action documentation](https://fluxcd.io/flux/flux-gh-action/).
|
||||||
steps:
|
|
||||||
- name: Setup Flux CLI
|
|
||||||
uses: fluxcd/flux2/action@main
|
|
||||||
with:
|
|
||||||
arch: arm64 # can be amd64, arm64 or arm
|
|
||||||
```
|
|
||||||
|
|
||||||
You can download a specific version with:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
steps:
|
|
||||||
- name: Setup Flux CLI
|
|
||||||
uses: fluxcd/flux2/action@main
|
|
||||||
with:
|
|
||||||
version: 0.32.0
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also authenticate against the GitHub API using GitHub Actions' `GITHUB_TOKEN` secret.
|
|
||||||
|
|
||||||
For more information, please [read about the GitHub token secret](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret).
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
steps:
|
|
||||||
- name: Setup Flux CLI
|
|
||||||
uses: fluxcd/flux2/action@main
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is useful if you are seeing failures on shared runners, those failures are usually API limits being hit.
|
|
||||||
|
|
||||||
### Automate Flux updates
|
|
||||||
|
|
||||||
Example workflow for updating Flux's components generated with `flux bootstrap --path=clusters/production`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: update-flux
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 * * * *"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
components:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Setup Flux CLI
|
|
||||||
uses: fluxcd/flux2/action@main
|
|
||||||
- name: Check for updates
|
|
||||||
id: update
|
|
||||||
run: |
|
|
||||||
flux install \
|
|
||||||
--export > ./clusters/production/flux-system/gotk-components.yaml
|
|
||||||
|
|
||||||
VERSION="$(flux -v)"
|
|
||||||
echo "flux_version=$VERSION" >> $GITHUB_OUTPUT
|
|
||||||
- name: Create Pull Request
|
|
||||||
uses: peter-evans/create-pull-request@v4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
branch: update-flux
|
|
||||||
commit-message: Update to ${{ steps.update.outputs.flux_version }}
|
|
||||||
title: Update to ${{ steps.update.outputs.flux_version }}
|
|
||||||
body: |
|
|
||||||
${{ steps.update.outputs.flux_version }}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Push Kubernetes manifests to container registries
|
|
||||||
|
|
||||||
Example workflow for publishing Kubernetes manifests bundled as OCI artifacts to GitHub Container Registry:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: push-artifact-staging
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
packages: write # needed for ghcr.io access
|
|
||||||
|
|
||||||
env:
|
|
||||||
OCI_REPO: "oci://ghcr.io/my-org/manifests/${{ github.event.repository.name }}"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
kubernetes:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Setup Flux CLI
|
|
||||||
uses: fluxcd/flux2/action@main
|
|
||||||
- name: Login to GHCR
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Generate manifests
|
|
||||||
run: |
|
|
||||||
kustomize build ./manifests/staging > ./deploy/app.yaml
|
|
||||||
- name: Push manifests
|
|
||||||
run: |
|
|
||||||
flux push artifact $OCI_REPO:$(git rev-parse --short HEAD) \
|
|
||||||
--path="./deploy" \
|
|
||||||
--source="$(git config --get remote.origin.url)" \
|
|
||||||
--revision="$(git branch --show-current)@sha1:$(git rev-parse HEAD)"
|
|
||||||
- name: Deploy manifests to staging
|
|
||||||
run: |
|
|
||||||
flux tag artifact $OCI_REPO:$(git rev-parse --short HEAD) --tag staging
|
|
||||||
```
|
|
||||||
|
|
||||||
### Push and sign Kubernetes manifests to container registries
|
|
||||||
|
|
||||||
Example workflow for publishing Kubernetes manifests bundled as OCI artifacts
|
|
||||||
which are signed with Cosign and GitHub OIDC:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: push-sign-artifact
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
packages: write # needed for ghcr.io access
|
|
||||||
id-token: write # needed for keyless signing
|
|
||||||
|
|
||||||
env:
|
|
||||||
OCI_REPO: "oci://ghcr.io/my-org/manifests/${{ github.event.repository.name }}"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
kubernetes:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Setup Flux CLI
|
|
||||||
uses: fluxcd/flux2/action@main
|
|
||||||
- name: Setup Cosign
|
|
||||||
uses: sigstore/cosign-installer@main
|
|
||||||
- name: Login to GHCR
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Push and sign manifests
|
|
||||||
run: |
|
|
||||||
digest_url=$(flux push artifact \
|
|
||||||
$OCI_REPO:$(git rev-parse --short HEAD) \
|
|
||||||
--path="./manifests" \
|
|
||||||
--source="$(git config --get remote.origin.url)" \
|
|
||||||
--revision="$(git branch --show-current)@sha1:$(git rev-parse HEAD)" |\
|
|
||||||
jq -r '. | .repository + "@" + .digest')
|
|
||||||
|
|
||||||
cosign sign $digest_url
|
|
||||||
```
|
|
||||||
|
|
||||||
### End-to-end testing
|
|
||||||
|
|
||||||
Example workflow for running Flux in Kubernetes Kind:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: e2e
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
kubernetes:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Setup Flux CLI
|
|
||||||
uses: fluxcd/flux2/action@main
|
|
||||||
- name: Setup Kubernetes Kind
|
|
||||||
uses: engineerd/setup-kind@v0.5.0
|
|
||||||
- name: Install Flux in Kubernetes Kind
|
|
||||||
run: flux install
|
|
||||||
```
|
|
||||||
|
|
||||||
A complete e2e testing workflow is available here
|
|
||||||
[flux2-kustomize-helm-example](https://github.com/fluxcd/flux2-kustomize-helm-example/blob/main/.github/workflows/e2e.yaml)
|
|
||||||
|
|||||||
@@ -1,64 +1,120 @@
|
|||||||
name: Setup Flux CLI
|
name: Setup Flux CLI
|
||||||
description: A GitHub Action for running Flux commands
|
description: A GitHub Action for installing the Flux CLI
|
||||||
author: Stefan Prodan
|
author: Flux project
|
||||||
branding:
|
branding:
|
||||||
color: blue
|
color: blue
|
||||||
icon: command
|
icon: command
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: "Flux version e.g. 0.8.0 (defaults to latest stable release)"
|
description: "Flux version e.g. 2.0.0 (defaults to latest stable release)"
|
||||||
required: false
|
required: false
|
||||||
arch:
|
arch:
|
||||||
description: "arch can be amd64, arm64 or arm"
|
description: "arch can be amd64, arm64 or arm"
|
||||||
required: true
|
required: false
|
||||||
default: "amd64"
|
deprecationMessage: "No longer required, action will now detect runner arch."
|
||||||
bindir:
|
bindir:
|
||||||
description: "Optional location of the Flux binary. Will not use sudo if set. Updates System Path."
|
description: "Alternative location for the Flux binary, defaults to path relative to $RUNNER_TOOL_CACHE."
|
||||||
required: false
|
required: false
|
||||||
token:
|
token:
|
||||||
description: "GitHub Token used to authentication against the API (generally only needed to prevent quota limit errors)"
|
description: "Token used to authentication against the GitHub.com API. Defaults to the token from the GitHub context of the workflow."
|
||||||
required: false
|
required: false
|
||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- name: "Download flux binary to tmp"
|
- name: "Download the binary to the runner's cache dir"
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ARCH=${{ inputs.arch }}
|
|
||||||
VERSION=${{ inputs.version }}
|
VERSION=${{ inputs.version }}
|
||||||
TOKEN=${{ inputs.token }}
|
|
||||||
|
|
||||||
if [ -z "${VERSION}" ]; then
|
TOKEN=${{ inputs.token }}
|
||||||
if [ -n "${TOKEN}" ]; then
|
if [[ -z "$TOKEN" ]]; then
|
||||||
VERSION_SLUG=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest --silent --location --header "Authorization: token ${TOKEN}" | grep tag_name)
|
TOKEN=${{ github.token }}
|
||||||
else
|
fi
|
||||||
# With no GITHUB_TOKEN you will experience occasional failures due to rate limiting
|
|
||||||
# Ref: https://github.com/fluxcd/flux2/issues/3509#issuecomment-1400820992
|
if [[ -z "$VERSION" ]] || [[ "$VERSION" = "latest" ]]; then
|
||||||
VERSION_SLUG=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest --silent --location | grep tag_name)
|
VERSION=$(curl -fsSL -H "Authorization: token ${TOKEN}" https://api.github.com/repos/fluxcd/flux2/releases/latest | grep tag_name | cut -d '"' -f 4)
|
||||||
|
fi
|
||||||
|
if [[ -z "$VERSION" ]]; then
|
||||||
|
echo "Unable to determine Flux CLI version"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ $VERSION = v* ]]; then
|
||||||
|
VERSION="${VERSION:1}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
OS=$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
if [[ "$OS" == "macos" ]]; then
|
||||||
|
OS="darwin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARCH=$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
if [[ "$ARCH" == "x64" ]]; then
|
||||||
|
ARCH="amd64"
|
||||||
|
elif [[ "$ARCH" == "x86" ]]; then
|
||||||
|
ARCH="386"
|
||||||
|
fi
|
||||||
|
|
||||||
|
FLUX_EXEC_FILE="flux"
|
||||||
|
if [[ "$OS" == "windows" ]]; then
|
||||||
|
FLUX_EXEC_FILE="${FLUX_EXEC_FILE}.exe"
|
||||||
|
fi
|
||||||
|
|
||||||
|
FLUX_TOOL_DIR=${{ inputs.bindir }}
|
||||||
|
if [[ -z "$FLUX_TOOL_DIR" ]]; then
|
||||||
|
FLUX_TOOL_DIR="${RUNNER_TOOL_CACHE}/flux2/${VERSION}/${OS}/${ARCH}"
|
||||||
|
fi
|
||||||
|
if [[ ! -x "$FLUX_TOOL_DIR/FLUX_EXEC_FILE" ]]; then
|
||||||
|
DL_DIR="$(mktemp -dt flux2-XXXXXX)"
|
||||||
|
trap 'rm -rf $DL_DIR' EXIT
|
||||||
|
|
||||||
|
echo "Downloading flux ${VERSION} for ${OS}/${ARCH}"
|
||||||
|
FLUX_TARGET_FILE="flux_${VERSION}_${OS}_${ARCH}.tar.gz"
|
||||||
|
if [[ "$OS" == "windows" ]]; then
|
||||||
|
FLUX_TARGET_FILE="flux_${VERSION}_${OS}_${ARCH}.zip"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
VERSION=$(echo "${VERSION_SLUG}" | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-)
|
FLUX_CHECKSUMS_FILE="flux_${VERSION}_checksums.txt"
|
||||||
|
|
||||||
|
FLUX_DOWNLOAD_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/"
|
||||||
|
|
||||||
|
curl -fsSL -o "$DL_DIR/$FLUX_TARGET_FILE" "$FLUX_DOWNLOAD_URL/$FLUX_TARGET_FILE"
|
||||||
|
curl -fsSL -o "$DL_DIR/$FLUX_CHECKSUMS_FILE" "$FLUX_DOWNLOAD_URL/$FLUX_CHECKSUMS_FILE"
|
||||||
|
|
||||||
|
echo "Verifying checksum"
|
||||||
|
sum=""
|
||||||
|
if command -v openssl > /dev/null; then
|
||||||
|
sum=$(openssl sha256 "$DL_DIR/$FLUX_TARGET_FILE" | awk '{print $2}')
|
||||||
|
elif command -v sha256sum > /dev/null; then
|
||||||
|
sum=$(sha256sum "$DL_DIR/$FLUX_TARGET_FILE" | awk '{print $1}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$sum" ]]; then
|
||||||
|
echo "Neither openssl nor sha256sum found. Cannot calculate checksum."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
expected_sum=$(grep " $FLUX_TARGET_FILE\$" "$DL_DIR/$FLUX_CHECKSUMS_FILE" | awk '{print $1}')
|
||||||
|
if [ "$sum" != "$expected_sum" ]; then
|
||||||
|
echo "SHA sum of ${FLUX_TARGET_FILE} does not match. Aborting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing flux to ${FLUX_TOOL_DIR}"
|
||||||
|
mkdir -p "$FLUX_TOOL_DIR"
|
||||||
|
|
||||||
|
if [[ "$OS" == "windows" ]]; then
|
||||||
|
unzip "$DL_DIR/$FLUX_TARGET_FILE" "$FLUX_EXEC_FILE" -d "$FLUX_TOOL_DIR"
|
||||||
|
else
|
||||||
|
tar xzf "$DL_DIR/$FLUX_TARGET_FILE" -C "$FLUX_TOOL_DIR" $FLUX_EXEC_FILE
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x "$FLUX_TOOL_DIR/$FLUX_EXEC_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
BIN_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_${ARCH}.tar.gz"
|
echo "Adding flux to path"
|
||||||
curl --silent --fail --location "${BIN_URL}" --output /tmp/flux.tar.gz
|
echo "$FLUX_TOOL_DIR" >> "$GITHUB_PATH"
|
||||||
mkdir -p /tmp/flux
|
|
||||||
tar -C /tmp/flux/ -zxvf /tmp/flux.tar.gz
|
- name: "Print installed flux version"
|
||||||
- name: "Copy Flux binary to execute location"
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
BINDIR=${{ inputs.bindir }}
|
|
||||||
if [ -z "${BINDIR}" ]; then
|
|
||||||
sudo cp /tmp/flux/flux /usr/local/bin
|
|
||||||
else
|
|
||||||
cp /tmp/flux/flux "${BINDIR}"
|
|
||||||
echo "${BINDIR}" >> $GITHUB_PATH
|
|
||||||
fi
|
|
||||||
- name: "Cleanup tmp"
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
rm -rf /tmp/flux/ /tmp/flux.tar.gz
|
|
||||||
- name: "Verify correct installation of binary"
|
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
flux -v
|
flux -v
|
||||||
|
|||||||
195
cmd/flux/audit.go
Normal file
195
cmd/flux/audit.go
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
v1 "k8s.io/api/apps/v1"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ctrlChecks = map[string]map[string]bool{
|
||||||
|
"helm-controller": {
|
||||||
|
"insecure-kubeconfig-exec": false,
|
||||||
|
"insecure-kubeconfig-tls": false,
|
||||||
|
},
|
||||||
|
"kustomize-controller": {
|
||||||
|
"insecure-kubeconfig-exec": false,
|
||||||
|
"insecure-kubeconfig-tls": false,
|
||||||
|
"no-remote-bases": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var multiTenancyCtrlChecks = map[string]map[string]bool{
|
||||||
|
"helm-controller": {
|
||||||
|
"no-cross-namespace-refs": true,
|
||||||
|
},
|
||||||
|
"kustomize-controller": {
|
||||||
|
"no-cross-namespace-refs": true,
|
||||||
|
},
|
||||||
|
"notification-controller": {
|
||||||
|
"no-cross-namespace-refs": true,
|
||||||
|
},
|
||||||
|
"image-reflector-controller": {
|
||||||
|
"no-cross-namespace-refs": true,
|
||||||
|
},
|
||||||
|
"image-automation-controller": {
|
||||||
|
"no-cross-namespace-refs": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var multiTenancyFlag bool
|
||||||
|
|
||||||
|
var auditCmd = &cobra.Command{
|
||||||
|
Use: "audit",
|
||||||
|
Short: "Audit the Flux installation for security best practices",
|
||||||
|
Long: withPreviewNote("TBD"),
|
||||||
|
Example: ` TBD`,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Actionf("Starting audit")
|
||||||
|
|
||||||
|
for ctrl, checks := range ctrlChecks {
|
||||||
|
if err := auditController(ctx, kubeClient, ctrl, checks); err != nil {
|
||||||
|
return fmt.Errorf("failed auditing %s: %w", ctrl, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := auditSecretDecryption(ctx, kubeClient); err != nil {
|
||||||
|
return fmt.Errorf("failed auditing Secret decryption: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if multiTenancyFlag {
|
||||||
|
logger.Actionf("Multi-tenancy lock-down")
|
||||||
|
for ctrl, checks := range multiTenancyCtrlChecks {
|
||||||
|
if err := auditController(ctx, kubeClient, ctrl, checks); err != nil {
|
||||||
|
return fmt.Errorf("failed auditing %s for multi-tenancy lock-down: %w", ctrl, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func auditSecretDecryption(ctx context.Context, c client.Client) error {
|
||||||
|
var ksl kustomizev1.KustomizationList
|
||||||
|
if err := c.List(ctx, &ksl); err != nil {
|
||||||
|
return fmt.Errorf("failed to retrieve Kustomizations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
success := true
|
||||||
|
for _, ks := range ksl.Items {
|
||||||
|
if ks.Status.Inventory == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ks.Spec.Decryption != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, e := range ks.Status.Inventory.Entries {
|
||||||
|
parts := strings.Split(e.ID, "_")
|
||||||
|
if parts[2] == "" && parts[3] == "Secret" {
|
||||||
|
success = false
|
||||||
|
logger.Warningf("%s/%s doesn't have Secret decryption configured", ks.Namespace, ks.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if success {
|
||||||
|
logger.Successf("Secret decryption is configured for all Kustomizations that create Secrets")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func auditController(ctx context.Context, c client.Client, name string, flags map[string]bool) error {
|
||||||
|
hcDeploys, err := getManagerArgs(ctx, c, name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get %s flags: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hcDeploys) == 0 {
|
||||||
|
logger.Warningf("No %s Deployment found, auditing skipped", name)
|
||||||
|
} else {
|
||||||
|
for name, args := range hcDeploys {
|
||||||
|
for flag, desired := range flags {
|
||||||
|
hcExec, err := assertBoolFlagValue(args, flag, desired)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed parsing %q args: %w", name, err)
|
||||||
|
}
|
||||||
|
if hcExec == desired {
|
||||||
|
logger.Successf("%s: %s is %t", name, flag, desired)
|
||||||
|
} else {
|
||||||
|
logger.Warningf("%s: %s should be %t", name, flag, desired)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getManagerArgs(ctx context.Context, c client.Client, component string) (map[string][]string, error) {
|
||||||
|
var deploys v1.DeploymentList
|
||||||
|
if err := c.List(ctx, &deploys, client.MatchingLabels{
|
||||||
|
"app.kubernetes.io/component": component,
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve %s deployments: %w", component, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make(map[string][]string, 0)
|
||||||
|
|
||||||
|
for _, deploy := range deploys.Items {
|
||||||
|
for _, ctr := range deploy.Spec.Template.Spec.Containers {
|
||||||
|
if ctr.Name == "manager" {
|
||||||
|
res[deploy.Name] = ctr.Args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertBoolFlagValue(args []string, flagName string, value bool) (bool, error) {
|
||||||
|
fs := pflag.NewFlagSet("tmp", pflag.ContinueOnError)
|
||||||
|
fs.ParseErrorsWhitelist.UnknownFlags = true
|
||||||
|
f := fs.BoolP(flagName, "", false, "")
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return false, fmt.Errorf("failed parsing args: %w", err)
|
||||||
|
}
|
||||||
|
return *f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
auditCmd.Flags().BoolVar(&multiTenancyFlag, "multi-tenancy", false, "Enable additional audit checks for multi-tenant clusters.")
|
||||||
|
rootCmd.AddCommand(auditCmd)
|
||||||
|
}
|
||||||
@@ -17,11 +17,15 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/v2/internal/flags"
|
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
@@ -72,6 +76,8 @@ type bootstrapFlags struct {
|
|||||||
gpgPassphrase string
|
gpgPassphrase string
|
||||||
gpgKeyID string
|
gpgKeyID string
|
||||||
|
|
||||||
|
force bool
|
||||||
|
|
||||||
commitMessageAppendix string
|
commitMessageAppendix string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +135,7 @@ func init() {
|
|||||||
|
|
||||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'")
|
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'")
|
||||||
|
|
||||||
|
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.force, "force", false, "override existing Flux installation if it's managed by a diffrent tool such as Helm")
|
||||||
bootstrapCmd.PersistentFlags().MarkHidden("manifests")
|
bootstrapCmd.PersistentFlags().MarkHidden("manifests")
|
||||||
|
|
||||||
rootCmd.AddCommand(bootstrapCmd)
|
rootCmd.AddCommand(bootstrapCmd)
|
||||||
@@ -188,3 +195,27 @@ func mapTeamSlice(s []string, defaultPermission string) map[string]string {
|
|||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// confirmBootstrap gets a confirmation for running bootstrap over an existing Flux installation.
|
||||||
|
// It returns a nil error if Flux is not installed or the user confirms overriding an existing installation
|
||||||
|
func confirmBootstrap(ctx context.Context, kubeClient client.Client) error {
|
||||||
|
installed := true
|
||||||
|
info, err := getFluxClusterInfo(ctx, kubeClient)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.IsNotFound(err) {
|
||||||
|
return fmt.Errorf("cluster info unavailable: %w", err)
|
||||||
|
}
|
||||||
|
installed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if installed {
|
||||||
|
err = confirmFluxInstallOverride(info)
|
||||||
|
if err != nil {
|
||||||
|
if err == promptui.ErrAbort {
|
||||||
|
return fmt.Errorf("bootstrap cancelled")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -124,6 +124,13 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !bootstrapArgs.force {
|
||||||
|
err = confirmBootstrap(ctx, kubeClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Manifest base
|
// Manifest base
|
||||||
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -146,6 +146,13 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !bootstrapArgs.force {
|
||||||
|
err = confirmBootstrap(ctx, kubeClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Manifest base
|
// Manifest base
|
||||||
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
275
cmd/flux/bootstrap_gitea.go
Normal file
275
cmd/flux/bootstrap_gitea.go
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/git"
|
||||||
|
"github.com/fluxcd/pkg/git/gogit"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||||
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/v2/pkg/bootstrap"
|
||||||
|
"github.com/fluxcd/flux2/v2/pkg/bootstrap/provider"
|
||||||
|
"github.com/fluxcd/flux2/v2/pkg/manifestgen"
|
||||||
|
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install"
|
||||||
|
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret"
|
||||||
|
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bootstrapGiteaCmd = &cobra.Command{
|
||||||
|
Use: "gitea",
|
||||||
|
Short: "Deploy Flux on a cluster connected to a Gitea repository",
|
||||||
|
Long: `The bootstrap gitea command creates the Gitea repository if it doesn't exists and
|
||||||
|
commits the Flux manifests to the specified branch.
|
||||||
|
Then it configures the target cluster to synchronize with that repository.
|
||||||
|
If the Flux components are present on the cluster,
|
||||||
|
the bootstrap command will perform an upgrade if needed.`,
|
||||||
|
Example: ` # Create a Gitea personal access token and export it as an env var
|
||||||
|
export GITEA_TOKEN=<my-token>
|
||||||
|
|
||||||
|
# Run bootstrap for a private repository owned by a Gitea organization
|
||||||
|
flux bootstrap gitea --owner=<organization> --repository=<repository name> --path=clusters/my-cluster
|
||||||
|
|
||||||
|
# Run bootstrap for a private repository and assign organization teams to it
|
||||||
|
flux bootstrap gitea --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug> --path=clusters/my-cluster
|
||||||
|
|
||||||
|
# Run bootstrap for a private repository and assign organization teams with their access level(e.g maintain, admin) to it
|
||||||
|
flux bootstrap gitea --owner=<organization> --repository=<repository name> --team=<team1 slug>:<access-level> --path=clusters/my-cluster
|
||||||
|
|
||||||
|
# Run bootstrap for a public repository on a personal account
|
||||||
|
flux bootstrap gitea --owner=<user> --repository=<repository name> --private=false --personal=true --path=clusters/my-cluster
|
||||||
|
|
||||||
|
# Run bootstrap for a private repository hosted on Gitea Enterprise using SSH auth
|
||||||
|
flux bootstrap gitea --owner=<organization> --repository=<repository name> --hostname=<domain> --ssh-hostname=<domain> --path=clusters/my-cluster
|
||||||
|
|
||||||
|
# Run bootstrap for a private repository hosted on Gitea Enterprise using HTTPS auth
|
||||||
|
flux bootstrap gitea --owner=<organization> --repository=<repository name> --hostname=<domain> --token-auth --path=clusters/my-cluster
|
||||||
|
|
||||||
|
# Run bootstrap for an existing repository with a branch named main
|
||||||
|
flux bootstrap gitea --owner=<organization> --repository=<repository name> --branch=main --path=clusters/my-cluster`,
|
||||||
|
RunE: bootstrapGiteaCmdRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
type giteaFlags struct {
|
||||||
|
owner string
|
||||||
|
repository string
|
||||||
|
interval time.Duration
|
||||||
|
personal bool
|
||||||
|
private bool
|
||||||
|
hostname string
|
||||||
|
path flags.SafeRelativePath
|
||||||
|
teams []string
|
||||||
|
readWriteKey bool
|
||||||
|
reconcile bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
gtDefaultPermission = "maintain"
|
||||||
|
gtDefaultDomain = "gitea.com"
|
||||||
|
gtTokenEnvVar = "GITEA_TOKEN"
|
||||||
|
)
|
||||||
|
|
||||||
|
var giteaArgs giteaFlags
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.owner, "owner", "", "Gitea user or organization name")
|
||||||
|
bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.repository, "repository", "", "Gitea repository name")
|
||||||
|
bootstrapGiteaCmd.Flags().StringSliceVar(&giteaArgs.teams, "team", []string{}, "Gitea team and the access to be given to it(team:maintain). Defaults to maintainer access if no access level is specified (also accepts comma-separated values)")
|
||||||
|
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.personal, "personal", false, "if true, the owner is assumed to be a Gitea user; otherwise an org")
|
||||||
|
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.private, "private", true, "if true, the repository is setup or configured as private")
|
||||||
|
bootstrapGiteaCmd.Flags().DurationVar(&giteaArgs.interval, "interval", time.Minute, "sync interval")
|
||||||
|
bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.hostname, "hostname", gtDefaultDomain, "Gitea hostname")
|
||||||
|
bootstrapGiteaCmd.Flags().Var(&giteaArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
|
||||||
|
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions")
|
||||||
|
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists")
|
||||||
|
|
||||||
|
bootstrapCmd.AddCommand(bootstrapGiteaCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
gtToken := os.Getenv(gtTokenEnvVar)
|
||||||
|
if gtToken == "" {
|
||||||
|
var err error
|
||||||
|
gtToken, err = readPasswordFromStdin("Please enter your Gitea personal access token (PAT): ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read token: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bootstrapValidate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manifest base
|
||||||
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
bootstrapArgs.version = ver
|
||||||
|
}
|
||||||
|
manifestsBase, err := buildEmbeddedManifestBase()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(manifestsBase)
|
||||||
|
|
||||||
|
var caBundle []byte
|
||||||
|
if bootstrapArgs.caFile != "" {
|
||||||
|
var err error
|
||||||
|
caBundle, err = os.ReadFile(bootstrapArgs.caFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Build Gitea provider
|
||||||
|
providerCfg := provider.Config{
|
||||||
|
Provider: provider.GitProviderGitea,
|
||||||
|
Hostname: giteaArgs.hostname,
|
||||||
|
Token: gtToken,
|
||||||
|
CaBundle: caBundle,
|
||||||
|
}
|
||||||
|
providerClient, err := provider.BuildGitProvider(providerCfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create temporary working dir: %w", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}
|
||||||
|
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{
|
||||||
|
Transport: git.HTTPS,
|
||||||
|
Username: giteaArgs.owner,
|
||||||
|
Password: gtToken,
|
||||||
|
CAFile: caBundle,
|
||||||
|
}, clientOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create a Git client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install manifest config
|
||||||
|
installOptions := install.Options{
|
||||||
|
BaseURL: rootArgs.defaults.BaseURL,
|
||||||
|
Version: bootstrapArgs.version,
|
||||||
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
|
Components: bootstrapComponents(),
|
||||||
|
Registry: bootstrapArgs.registry,
|
||||||
|
ImagePullSecret: bootstrapArgs.imagePullSecret,
|
||||||
|
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
|
||||||
|
NetworkPolicy: bootstrapArgs.networkPolicy,
|
||||||
|
LogLevel: bootstrapArgs.logLevel.String(),
|
||||||
|
NotificationController: rootArgs.defaults.NotificationController,
|
||||||
|
ManifestFile: rootArgs.defaults.ManifestFile,
|
||||||
|
Timeout: rootArgs.timeout,
|
||||||
|
TargetPath: giteaArgs.path.ToSlash(),
|
||||||
|
ClusterDomain: bootstrapArgs.clusterDomain,
|
||||||
|
TolerationKeys: bootstrapArgs.tolerationKeys,
|
||||||
|
}
|
||||||
|
if customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != "" {
|
||||||
|
installOptions.BaseURL = customBaseURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source generation and secret config
|
||||||
|
secretOpts := sourcesecret.Options{
|
||||||
|
Name: bootstrapArgs.secretName,
|
||||||
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
|
TargetPath: giteaArgs.path.ToSlash(),
|
||||||
|
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||||
|
}
|
||||||
|
if bootstrapArgs.tokenAuth {
|
||||||
|
secretOpts.Username = "git"
|
||||||
|
secretOpts.Password = gtToken
|
||||||
|
secretOpts.CAFile = caBundle
|
||||||
|
} else {
|
||||||
|
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
|
||||||
|
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
|
||||||
|
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
|
||||||
|
|
||||||
|
secretOpts.SSHHostname = giteaArgs.hostname
|
||||||
|
if bootstrapArgs.sshHostname != "" {
|
||||||
|
secretOpts.SSHHostname = bootstrapArgs.sshHostname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync manifest config
|
||||||
|
syncOpts := sync.Options{
|
||||||
|
Interval: giteaArgs.interval,
|
||||||
|
Name: *kubeconfigArgs.Namespace,
|
||||||
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
|
Branch: bootstrapArgs.branch,
|
||||||
|
Secret: bootstrapArgs.secretName,
|
||||||
|
TargetPath: giteaArgs.path.ToSlash(),
|
||||||
|
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
|
||||||
|
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
|
||||||
|
}
|
||||||
|
|
||||||
|
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap config
|
||||||
|
bootstrapOpts := []bootstrap.GitProviderOption{
|
||||||
|
bootstrap.WithProviderRepository(giteaArgs.owner, giteaArgs.repository, giteaArgs.personal),
|
||||||
|
bootstrap.WithBranch(bootstrapArgs.branch),
|
||||||
|
bootstrap.WithBootstrapTransportType("https"),
|
||||||
|
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||||
|
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
||||||
|
bootstrap.WithProviderTeamPermissions(mapTeamSlice(giteaArgs.teams, gtDefaultPermission)),
|
||||||
|
bootstrap.WithReadWriteKeyPermissions(giteaArgs.readWriteKey),
|
||||||
|
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
|
||||||
|
bootstrap.WithLogger(logger),
|
||||||
|
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
|
||||||
|
}
|
||||||
|
if bootstrapArgs.sshHostname != "" {
|
||||||
|
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
|
||||||
|
}
|
||||||
|
if bootstrapArgs.tokenAuth {
|
||||||
|
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https"))
|
||||||
|
}
|
||||||
|
if !giteaArgs.private {
|
||||||
|
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
|
||||||
|
}
|
||||||
|
if giteaArgs.reconcile {
|
||||||
|
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup bootstrapper with constructed configs
|
||||||
|
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run
|
||||||
|
return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
|
||||||
|
}
|
||||||
@@ -128,6 +128,13 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !bootstrapArgs.force {
|
||||||
|
err = confirmBootstrap(ctx, kubeClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Manifest base
|
// Manifest base
|
||||||
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -145,6 +145,13 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !bootstrapArgs.force {
|
||||||
|
err = confirmBootstrap(ctx, kubeClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Manifest base
|
// Manifest base
|
||||||
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
ociClient := oci.NewClient(oci.DefaultOptions())
|
ociClient := oci.NewClient(oci.DefaultOptions())
|
||||||
if err := ociClient.Build(buildArtifactArgs.output, path, buildArtifactArgs.ignorePaths); err != nil {
|
if err := ociClient.Build(buildArtifactArgs.output, path, buildArtifactArgs.ignorePaths); err != nil {
|
||||||
return fmt.Errorf("bulding artifact failed, error: %w", err)
|
return fmt.Errorf("building artifact failed, error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Successf("artifact created at %s", buildArtifactArgs.output)
|
logger.Successf("artifact created at %s", buildArtifactArgs.output)
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ spec:
|
|||||||
tmpl := map[string]string{
|
tmpl := map[string]string{
|
||||||
"fluxns": allocateNamespace("flux-system"),
|
"fluxns": allocateNamespace("flux-system"),
|
||||||
}
|
}
|
||||||
|
setup(t, tmpl)
|
||||||
|
|
||||||
testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-source.yaml", tmpl, t)
|
testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-source.yaml", tmpl, t)
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ type checkFlags struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var kubernetesConstraints = []string{
|
var kubernetesConstraints = []string{
|
||||||
">=1.20.6-0",
|
">=1.25.0-0",
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkArgs checkFlags
|
var checkArgs checkFlags
|
||||||
|
|||||||
110
cmd/flux/cluster_info.go
Normal file
110
cmd/flux/cluster_info.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/manifoldco/promptui"
|
||||||
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||||
|
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// bootstrapLabels are labels put on a resource by kustomize-controller. These labels on the CRD indicates
|
||||||
|
// that flux has been bootstrapped.
|
||||||
|
var bootstrapLabels = []string{
|
||||||
|
fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group),
|
||||||
|
fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group),
|
||||||
|
}
|
||||||
|
|
||||||
|
// fluxClusterInfo contains information about an existing flux installation on a cluster.
|
||||||
|
type fluxClusterInfo struct {
|
||||||
|
// bootstrapped indicates that Flux was installed using the `flux bootstrap` command.
|
||||||
|
bootstrapped bool
|
||||||
|
// managedBy is the name of the tool being used to manage the installation of Flux.
|
||||||
|
managedBy string
|
||||||
|
// version is the Flux version number in semver format.
|
||||||
|
version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFluxClusterInfo returns information on the Flux installation running on the cluster.
|
||||||
|
// If an error occurred, the returned error will be non-nil.
|
||||||
|
//
|
||||||
|
// This function retrieves the GitRepository CRD from the cluster and checks it
|
||||||
|
// for a set of labels used to determine the Flux version and how Flux was installed.
|
||||||
|
// It returns the NotFound error from the underlying library if it was unable to find
|
||||||
|
// the GitRepository CRD and this can be used to check if Flux is installed.
|
||||||
|
func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, error) {
|
||||||
|
var info fluxClusterInfo
|
||||||
|
crdMetadata := &metav1.PartialObjectMetadata{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: apiextensionsv1.SchemeGroupVersion.String(),
|
||||||
|
Kind: "CustomResourceDefinition",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("gitrepositories.%s", sourcev1.GroupVersion.Group),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := c.Get(ctx, client.ObjectKeyFromObject(crdMetadata), crdMetadata); err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info.version = crdMetadata.Labels["app.kubernetes.io/version"]
|
||||||
|
|
||||||
|
var present bool
|
||||||
|
for _, l := range bootstrapLabels {
|
||||||
|
_, present = crdMetadata.Labels[l]
|
||||||
|
}
|
||||||
|
if present {
|
||||||
|
info.bootstrapped = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// the `app.kubernetes.io` label is not set by flux but might be set by other
|
||||||
|
// tools used to install Flux e.g Helm.
|
||||||
|
if manager, ok := crdMetadata.Labels["app.kubernetes.io/managed-by"]; ok {
|
||||||
|
info.managedBy = manager
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirmFluxInstallOverride displays a prompt to the user so that they can confirm before overriding
|
||||||
|
// a Flux installation. It returns nil if the installation should continue,
|
||||||
|
// promptui.ErrAbort if the user doesn't confirm, or an error encountered.
|
||||||
|
func confirmFluxInstallOverride(info fluxClusterInfo) error {
|
||||||
|
// no need to display prompt if installation is managed by Flux
|
||||||
|
if installManagedByFlux(info.managedBy) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
display := fmt.Sprintf("Flux %s has been installed on this cluster with %s!", info.version, info.managedBy)
|
||||||
|
fmt.Fprintln(rootCmd.ErrOrStderr(), display)
|
||||||
|
prompt := promptui.Prompt{
|
||||||
|
Label: fmt.Sprintf("Are you sure you want to override the %s installation? Y/N", info.managedBy),
|
||||||
|
IsConfirm: true,
|
||||||
|
}
|
||||||
|
_, err := prompt.Run()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func installManagedByFlux(manager string) bool {
|
||||||
|
return manager == "" || manager == "flux"
|
||||||
|
}
|
||||||
130
cmd/flux/cluster_info_test.go
Normal file
130
cmd/flux/cluster_info_test.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||||
|
|
||||||
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||||
|
"github.com/fluxcd/pkg/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_getFluxClusterInfo(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
f, err := os.Open("./testdata/cluster_info/gitrepositories.yaml")
|
||||||
|
g.Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
objs, err := ssa.ReadObjects(f)
|
||||||
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
|
gitrepo := objs[0]
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
labels map[string]string
|
||||||
|
wantErr bool
|
||||||
|
wantInfo fluxClusterInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no git repository CRD present",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CRD with kustomize-controller labels",
|
||||||
|
labels: map[string]string{
|
||||||
|
fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group): "flux-system",
|
||||||
|
fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group): "flux-system",
|
||||||
|
"app.kubernetes.io/version": "v2.1.0",
|
||||||
|
},
|
||||||
|
wantInfo: fluxClusterInfo{
|
||||||
|
version: "v2.1.0",
|
||||||
|
bootstrapped: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CRD with kustomize-controller labels and managed-by label",
|
||||||
|
labels: map[string]string{
|
||||||
|
fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group): "flux-system",
|
||||||
|
fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group): "flux-system",
|
||||||
|
"app.kubernetes.io/version": "v2.1.0",
|
||||||
|
"app.kubernetes.io/managed-by": "flux",
|
||||||
|
},
|
||||||
|
wantInfo: fluxClusterInfo{
|
||||||
|
version: "v2.1.0",
|
||||||
|
bootstrapped: true,
|
||||||
|
managedBy: "flux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CRD with only managed-by label",
|
||||||
|
labels: map[string]string{
|
||||||
|
"app.kubernetes.io/version": "v2.1.0",
|
||||||
|
"app.kubernetes.io/managed-by": "helm",
|
||||||
|
},
|
||||||
|
wantInfo: fluxClusterInfo{
|
||||||
|
version: "v2.1.0",
|
||||||
|
managedBy: "helm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CRD with no labels",
|
||||||
|
labels: map[string]string{},
|
||||||
|
wantInfo: fluxClusterInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CRD with only version label",
|
||||||
|
labels: map[string]string{
|
||||||
|
"app.kubernetes.io/version": "v2.1.0",
|
||||||
|
},
|
||||||
|
wantInfo: fluxClusterInfo{
|
||||||
|
version: "v2.1.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
newscheme := runtime.NewScheme()
|
||||||
|
apiextensionsv1.AddToScheme(newscheme)
|
||||||
|
builder := fake.NewClientBuilder().WithScheme(newscheme)
|
||||||
|
if tt.labels != nil {
|
||||||
|
gitrepo.SetLabels(tt.labels)
|
||||||
|
builder = builder.WithRuntimeObjects(gitrepo)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := builder.Build()
|
||||||
|
info, err := getFluxClusterInfo(context.Background(), client)
|
||||||
|
if tt.wantErr {
|
||||||
|
g.Expect(err).To(HaveOccurred())
|
||||||
|
g.Expect(errors.IsNotFound(err)).To(BeTrue())
|
||||||
|
} else {
|
||||||
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Expect(info).To(BeEquivalentTo(tt.wantInfo))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -131,8 +131,8 @@ func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for %s reconciliation", names.kind)
|
logger.Waitingf("waiting for %s reconciliation", names.kind)
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isReady(ctx, kubeClient, namespacedName, object)); err != nil {
|
isReady(kubeClient, namespacedName, object)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("%s reconciliation completed", names.kind)
|
logger.Successf("%s reconciliation completed", names.kind)
|
||||||
@@ -165,6 +165,6 @@ func parseLabels() (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateObjectName(name string) bool {
|
func validateObjectName(name string) bool {
|
||||||
r := regexp.MustCompile("^[a-z0-9]([a-z0-9\\-]){0,61}[a-z0-9]$")
|
r := regexp.MustCompile(`^[a-z0-9]([a-z0-9\-]){0,61}[a-z0-9]$`)
|
||||||
return r.MatchString(name)
|
return r.MatchString(name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,8 +132,8 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for Alert reconciliation")
|
logger.Waitingf("waiting for Alert reconciliation")
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
|
isAlertReady(kubeClient, namespacedName, &alert)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("Alert %s is ready", name)
|
logger.Successf("Alert %s is ready", name)
|
||||||
@@ -171,9 +171,8 @@ func upsertAlert(ctx context.Context, kubeClient client.Client,
|
|||||||
return namespacedName, nil
|
return namespacedName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAlertReady(ctx context.Context, kubeClient client.Client,
|
func isAlertReady(kubeClient client.Client, namespacedName types.NamespacedName, alert *notificationv1b2.Alert) wait.ConditionWithContextFunc {
|
||||||
namespacedName types.NamespacedName, alert *notificationv1b2.Alert) wait.ConditionFunc {
|
return func(ctx context.Context) (bool, error) {
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, alert)
|
err := kubeClient.Get(ctx, namespacedName, alert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for Provider reconciliation")
|
logger.Waitingf("waiting for Provider reconciliation")
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isAlertProviderReady(ctx, kubeClient, namespacedName, &provider)); err != nil {
|
isAlertProviderReady(kubeClient, namespacedName, &provider)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,9 +168,8 @@ func upsertAlertProvider(ctx context.Context, kubeClient client.Client,
|
|||||||
return namespacedName, nil
|
return namespacedName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAlertProviderReady(ctx context.Context, kubeClient client.Client,
|
func isAlertProviderReady(kubeClient client.Client, namespacedName types.NamespacedName, provider *notificationv1.Provider) wait.ConditionWithContextFunc {
|
||||||
namespacedName types.NamespacedName, provider *notificationv1.Provider) wait.ConditionFunc {
|
return func(ctx context.Context) (bool, error) {
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, provider)
|
err := kubeClient.Get(ctx, namespacedName, provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -303,8 +303,8 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for HelmRelease reconciliation")
|
logger.Waitingf("waiting for HelmRelease reconciliation")
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isHelmReleaseReady(ctx, kubeClient, namespacedName, &helmRelease)); err != nil {
|
isHelmReleaseReady(kubeClient, namespacedName, &helmRelease)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("HelmRelease %s is ready", name)
|
logger.Successf("HelmRelease %s is ready", name)
|
||||||
@@ -344,9 +344,8 @@ func upsertHelmRelease(ctx context.Context, kubeClient client.Client,
|
|||||||
return namespacedName, nil
|
return namespacedName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isHelmReleaseReady(ctx context.Context, kubeClient client.Client,
|
func isHelmReleaseReady(kubeClient client.Client, namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) wait.ConditionWithContextFunc {
|
||||||
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) wait.ConditionFunc {
|
return func(ctx context.Context) (bool, error) {
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, helmRelease)
|
err := kubeClient.Get(ctx, namespacedName, helmRelease)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -54,13 +54,12 @@ the status of the object.`),
|
|||||||
RunE: createImagePolicyRun}
|
RunE: createImagePolicyRun}
|
||||||
|
|
||||||
type imagePolicyFlags struct {
|
type imagePolicyFlags struct {
|
||||||
imageRef string
|
imageRef string
|
||||||
semver string
|
semver string
|
||||||
alpha string
|
alpha string
|
||||||
numeric string
|
numeric string
|
||||||
filterRegex string
|
filterRegex string
|
||||||
filterExtract string
|
filterExtract string
|
||||||
filterNumerical string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var imagePolicyArgs = imagePolicyFlags{}
|
var imagePolicyArgs = imagePolicyFlags{}
|
||||||
@@ -183,7 +182,6 @@ func validateExtractStr(template string, capNames []string) error {
|
|||||||
name, num, rest, ok := extract(template)
|
name, num, rest, ok := extract(template)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Malformed extract string, assume user didn't want this
|
// Malformed extract string, assume user didn't want this
|
||||||
template = template[1:]
|
|
||||||
return fmt.Errorf("--filter-extract is malformed")
|
return fmt.Errorf("--filter-extract is malformed")
|
||||||
}
|
}
|
||||||
template = rest
|
template = rest
|
||||||
|
|||||||
@@ -263,8 +263,8 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for Kustomization reconciliation")
|
logger.Waitingf("waiting for Kustomization reconciliation")
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isKustomizationReady(ctx, kubeClient, namespacedName, &kustomization)); err != nil {
|
isKustomizationReady(kubeClient, namespacedName, &kustomization)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("Kustomization %s is ready", name)
|
logger.Successf("Kustomization %s is ready", name)
|
||||||
@@ -304,9 +304,8 @@ func upsertKustomization(ctx context.Context, kubeClient client.Client,
|
|||||||
return namespacedName, nil
|
return namespacedName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isKustomizationReady(ctx context.Context, kubeClient client.Client,
|
func isKustomizationReady(kubeClient client.Client, namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) wait.ConditionWithContextFunc {
|
||||||
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) wait.ConditionFunc {
|
return func(ctx context.Context) (bool, error) {
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, kustomization)
|
err := kubeClient.Get(ctx, namespacedName, kustomization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -139,8 +139,8 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for Receiver reconciliation")
|
logger.Waitingf("waiting for Receiver reconciliation")
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
|
isReceiverReady(kubeClient, namespacedName, &receiver)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("Receiver %s is ready", name)
|
logger.Successf("Receiver %s is ready", name)
|
||||||
@@ -180,9 +180,8 @@ func upsertReceiver(ctx context.Context, kubeClient client.Client,
|
|||||||
return namespacedName, nil
|
return namespacedName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isReceiverReady(ctx context.Context, kubeClient client.Client,
|
func isReceiverReady(kubeClient client.Client, namespacedName types.NamespacedName, receiver *notificationv1.Receiver) wait.ConditionWithContextFunc {
|
||||||
namespacedName types.NamespacedName, receiver *notificationv1.Receiver) wait.ConditionFunc {
|
return func(ctx context.Context) (bool, error) {
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, receiver)
|
err := kubeClient.Get(ctx, namespacedName, receiver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ type secretGitFlags struct {
|
|||||||
rsaBits flags.RSAKeyBits
|
rsaBits flags.RSAKeyBits
|
||||||
ecdsaCurve flags.ECDSACurve
|
ecdsaCurve flags.ECDSACurve
|
||||||
caFile string
|
caFile string
|
||||||
|
caCrtFile string
|
||||||
privateKeyFile string
|
privateKeyFile string
|
||||||
bearerToken string
|
bearerToken string
|
||||||
}
|
}
|
||||||
@@ -102,6 +103,7 @@ func init() {
|
|||||||
createSecretGitCmd.Flags().Var(&secretGitArgs.rsaBits, "ssh-rsa-bits", secretGitArgs.rsaBits.Description())
|
createSecretGitCmd.Flags().Var(&secretGitArgs.rsaBits, "ssh-rsa-bits", secretGitArgs.rsaBits.Description())
|
||||||
createSecretGitCmd.Flags().Var(&secretGitArgs.ecdsaCurve, "ssh-ecdsa-curve", secretGitArgs.ecdsaCurve.Description())
|
createSecretGitCmd.Flags().Var(&secretGitArgs.ecdsaCurve, "ssh-ecdsa-curve", secretGitArgs.ecdsaCurve.Description())
|
||||||
createSecretGitCmd.Flags().StringVar(&secretGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
|
createSecretGitCmd.Flags().StringVar(&secretGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
|
||||||
|
createSecretGitCmd.Flags().StringVar(&secretGitArgs.caCrtFile, "ca-crt-file", "", "path to TLS CA certificate file used for validating self-signed certificates; takes precedence over --ca-file")
|
||||||
createSecretGitCmd.Flags().StringVar(&secretGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
|
createSecretGitCmd.Flags().StringVar(&secretGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
|
||||||
createSecretGitCmd.Flags().StringVar(&secretGitArgs.bearerToken, "bearer-token", "", "bearer authentication token")
|
createSecretGitCmd.Flags().StringVar(&secretGitArgs.bearerToken, "bearer-token", "", "bearer authentication token")
|
||||||
|
|
||||||
@@ -160,12 +162,18 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
if secretGitArgs.username != "" && secretGitArgs.password != "" && secretGitArgs.bearerToken != "" {
|
if secretGitArgs.username != "" && secretGitArgs.password != "" && secretGitArgs.bearerToken != "" {
|
||||||
return fmt.Errorf("user credentials and bearer token cannot be used together")
|
return fmt.Errorf("user credentials and bearer token cannot be used together")
|
||||||
}
|
}
|
||||||
if secretGitArgs.caFile != "" {
|
|
||||||
caBundle, err := os.ReadFile(secretGitArgs.caFile)
|
// --ca-crt-file takes precedence over --ca-file.
|
||||||
|
if secretGitArgs.caCrtFile != "" {
|
||||||
|
opts.CACrt, err = os.ReadFile(secretGitArgs.caCrtFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
} else if secretGitArgs.caFile != "" {
|
||||||
|
opts.CAFile, err = os.ReadFile(secretGitArgs.caFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
}
|
}
|
||||||
opts.CAFile = caBundle
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateGitSecret(t *testing.T) {
|
func TestCreateGitSecret(t *testing.T) {
|
||||||
|
file, err := os.CreateTemp(t.TempDir(), "ca-crt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not create CA certificate file")
|
||||||
|
}
|
||||||
|
_, err = file.Write([]byte("ca-data"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not write to CA certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args string
|
args string
|
||||||
@@ -35,6 +46,11 @@ func TestCreateGitSecret(t *testing.T) {
|
|||||||
args: "create secret git bearer-token-auth --url=https://github.com/stefanprodan/podinfo --bearer-token=ghp_baR2qnFF0O41WlucePL3udt2N9vVZS4R0hAS --namespace=my-namespace --export",
|
args: "create secret git bearer-token-auth --url=https://github.com/stefanprodan/podinfo --bearer-token=ghp_baR2qnFF0O41WlucePL3udt2N9vVZS4R0hAS --namespace=my-namespace --export",
|
||||||
assert: assertGoldenFile("testdata/create_secret/git/git-bearer-token.yaml"),
|
assert: assertGoldenFile("testdata/create_secret/git/git-bearer-token.yaml"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "git authentication with CA certificate",
|
||||||
|
args: fmt.Sprintf("create secret git ca-crt --url=https://github.com/stefanprodan/podinfo --password=my-password --username=my-username --ca-crt-file=%s --namespace=my-namespace --export", file.Name()),
|
||||||
|
assert: assertGoldenFile("testdata/create_secret/git/secret-ca-crt.yaml"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "git authentication with basic auth and bearer token",
|
name: "git authentication with basic auth and bearer token",
|
||||||
args: "create secret git podinfo-auth --url=https://github.com/stefanprodan/podinfo --username=aaa --password=zzzz --bearer-token=aaaa --namespace=my-namespace --export",
|
args: "create secret git podinfo-auth --url=https://github.com/stefanprodan/podinfo --username=aaa --password=zzzz --bearer-token=aaaa --namespace=my-namespace --export",
|
||||||
|
|||||||
@@ -41,15 +41,8 @@ var createSecretHelmCmd = &cobra.Command{
|
|||||||
--export > repo-auth.yaml
|
--export > repo-auth.yaml
|
||||||
|
|
||||||
sops --encrypt --encrypted-regex '^(data|stringData)$' \
|
sops --encrypt --encrypted-regex '^(data|stringData)$' \
|
||||||
--in-place repo-auth.yaml
|
--in-place repo-auth.yaml`,
|
||||||
|
|
||||||
# Create a Helm authentication secret using a custom TLS cert
|
|
||||||
flux create secret helm repo-auth \
|
|
||||||
--username=username \
|
|
||||||
--password=password \
|
|
||||||
--cert-file=./cert.crt \
|
|
||||||
--key-file=./key.crt \
|
|
||||||
--ca-file=./ca.crt`,
|
|
||||||
RunE: createSecretHelmCmdRun,
|
RunE: createSecretHelmCmdRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +55,16 @@ type secretHelmFlags struct {
|
|||||||
var secretHelmArgs secretHelmFlags
|
var secretHelmArgs secretHelmFlags
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
createSecretHelmCmd.Flags().StringVarP(&secretHelmArgs.username, "username", "u", "", "basic authentication username")
|
flags := createSecretHelmCmd.Flags()
|
||||||
createSecretHelmCmd.Flags().StringVarP(&secretHelmArgs.password, "password", "p", "", "basic authentication password")
|
flags.StringVarP(&secretHelmArgs.username, "username", "u", "", "basic authentication username")
|
||||||
initSecretTLSFlags(createSecretHelmCmd.Flags(), &secretHelmArgs.secretTLSFlags)
|
flags.StringVarP(&secretHelmArgs.password, "password", "p", "", "basic authentication password")
|
||||||
|
|
||||||
|
initSecretDeprecatedTLSFlags(flags, &secretHelmArgs.secretTLSFlags)
|
||||||
|
deprecationMsg := "please use the command `flux create secret tls` to generate TLS secrets"
|
||||||
|
flags.MarkDeprecated("cert-file", deprecationMsg)
|
||||||
|
flags.MarkDeprecated("key-file", deprecationMsg)
|
||||||
|
flags.MarkDeprecated("ca-file", deprecationMsg)
|
||||||
|
|
||||||
createSecretCmd.AddCommand(createSecretHelmCmd)
|
createSecretCmd.AddCommand(createSecretHelmCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,9 @@ var createSecretTLSCmd = &cobra.Command{
|
|||||||
# Files are expected to be PEM-encoded.
|
# Files are expected to be PEM-encoded.
|
||||||
flux create secret tls certs \
|
flux create secret tls certs \
|
||||||
--namespace=my-namespace \
|
--namespace=my-namespace \
|
||||||
--cert-file=./client.crt \
|
--tls-crt-file=./client.crt \
|
||||||
--key-file=./client.key \
|
--tls-key-file=./client.key \
|
||||||
|
--ca-crt-file=./ca.crt \
|
||||||
--export > certs.yaml
|
--export > certs.yaml
|
||||||
|
|
||||||
sops --encrypt --encrypted-regex '^(data|stringData)$' \
|
sops --encrypt --encrypted-regex '^(data|stringData)$' \
|
||||||
@@ -48,22 +49,37 @@ var createSecretTLSCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type secretTLSFlags struct {
|
type secretTLSFlags struct {
|
||||||
certFile string
|
certFile string
|
||||||
keyFile string
|
keyFile string
|
||||||
caFile string
|
caFile string
|
||||||
|
caCrtFile string
|
||||||
|
tlsKeyFile string
|
||||||
|
tlsCrtFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
var secretTLSArgs secretTLSFlags
|
var secretTLSArgs secretTLSFlags
|
||||||
|
|
||||||
func initSecretTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) {
|
func initSecretDeprecatedTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) {
|
||||||
flags.StringVar(&args.certFile, "cert-file", "", "TLS authentication cert file path")
|
flags.StringVar(&args.certFile, "cert-file", "", "TLS authentication cert file path")
|
||||||
flags.StringVar(&args.keyFile, "key-file", "", "TLS authentication key file path")
|
flags.StringVar(&args.keyFile, "key-file", "", "TLS authentication key file path")
|
||||||
flags.StringVar(&args.caFile, "ca-file", "", "TLS authentication CA file path")
|
flags.StringVar(&args.caFile, "ca-file", "", "TLS authentication CA file path")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initSecretTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) {
|
||||||
|
flags.StringVar(&args.tlsCrtFile, "tls-crt-file", "", "TLS authentication cert file path")
|
||||||
|
flags.StringVar(&args.tlsKeyFile, "tls-key-file", "", "TLS authentication key file path")
|
||||||
|
flags.StringVar(&args.caCrtFile, "ca-crt-file", "", "TLS authentication CA file path")
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flags := createSecretTLSCmd.Flags()
|
flags := createSecretTLSCmd.Flags()
|
||||||
|
initSecretDeprecatedTLSFlags(flags, &secretTLSArgs)
|
||||||
initSecretTLSFlags(flags, &secretTLSArgs)
|
initSecretTLSFlags(flags, &secretTLSArgs)
|
||||||
|
|
||||||
|
flags.MarkDeprecated("cert-file", "please use --tls-crt-file instead")
|
||||||
|
flags.MarkDeprecated("key-file", "please use --tls-key-file instead")
|
||||||
|
flags.MarkDeprecated("ca-file", "please use --ca-crt-file instead")
|
||||||
|
|
||||||
createSecretCmd.AddCommand(createSecretTLSCmd)
|
createSecretCmd.AddCommand(createSecretTLSCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,33 +91,40 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
caBundle := []byte{}
|
opts := sourcesecret.Options{
|
||||||
if secretTLSArgs.caFile != "" {
|
Name: name,
|
||||||
var err error
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
caBundle, err = os.ReadFile(secretTLSArgs.caFile)
|
Labels: labels,
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretTLSArgs.caCrtFile != "" {
|
||||||
|
opts.CACrt, err = os.ReadFile(secretTLSArgs.caCrtFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
|
}
|
||||||
|
} else if secretTLSArgs.caFile != "" {
|
||||||
|
opts.CAFile, err = os.ReadFile(secretTLSArgs.caFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var certFile, keyFile []byte
|
if secretTLSArgs.tlsCrtFile != "" && secretTLSArgs.tlsKeyFile != "" {
|
||||||
if secretTLSArgs.certFile != "" && secretTLSArgs.keyFile != "" {
|
if opts.TLSCrt, err = os.ReadFile(secretTLSArgs.tlsCrtFile); err != nil {
|
||||||
if certFile, err = os.ReadFile(secretTLSArgs.certFile); err != nil {
|
|
||||||
return fmt.Errorf("failed to read cert file: %w", err)
|
return fmt.Errorf("failed to read cert file: %w", err)
|
||||||
}
|
}
|
||||||
if keyFile, err = os.ReadFile(secretTLSArgs.keyFile); err != nil {
|
if opts.TLSKey, err = os.ReadFile(secretTLSArgs.tlsKeyFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to read key file: %w", err)
|
||||||
|
}
|
||||||
|
} else if secretTLSArgs.certFile != "" && secretTLSArgs.keyFile != "" {
|
||||||
|
if opts.CertFile, err = os.ReadFile(secretTLSArgs.certFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to read cert file: %w", err)
|
||||||
|
}
|
||||||
|
if opts.KeyFile, err = os.ReadFile(secretTLSArgs.keyFile); err != nil {
|
||||||
return fmt.Errorf("failed to read key file: %w", err)
|
return fmt.Errorf("failed to read key file: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := sourcesecret.Options{
|
|
||||||
Name: name,
|
|
||||||
Namespace: *kubeconfigArgs.Namespace,
|
|
||||||
Labels: labels,
|
|
||||||
CAFile: caBundle,
|
|
||||||
CertFile: certFile,
|
|
||||||
KeyFile: keyFile,
|
|
||||||
}
|
|
||||||
secret, err := sourcesecret.Generate(opts)
|
secret, err := sourcesecret.Generate(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateTlsSecretNoArgs(t *testing.T) {
|
func TestCreateTlsSecret(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args string
|
args string
|
||||||
@@ -15,9 +15,13 @@ func TestCreateTlsSecretNoArgs(t *testing.T) {
|
|||||||
assert: assertError("name is required"),
|
assert: assertError("name is required"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "create secret tls certs --namespace=my-namespace --cert-file=./testdata/create_secret/tls/test-cert.pem --key-file=./testdata/create_secret/tls/test-key.pem --export",
|
args: "create secret tls certs --namespace=my-namespace --tls-crt-file=./testdata/create_secret/tls/test-cert.pem --tls-key-file=./testdata/create_secret/tls/test-key.pem --ca-crt-file=./testdata/create_secret/tls/test-ca.pem --export",
|
||||||
assert: assertGoldenFile("testdata/create_secret/tls/secret-tls.yaml"),
|
assert: assertGoldenFile("testdata/create_secret/tls/secret-tls.yaml"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
args: "create secret tls certs --namespace=my-namespace --cert-file=./testdata/create_secret/tls/test-cert.pem --key-file=./testdata/create_secret/tls/test-key.pem --ca-file=./testdata/create_secret/tls/test-ca.pem --export",
|
||||||
|
assert: assertGoldenFile("testdata/create_secret/tls/deprecated-secret-tls.yaml"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -204,8 +204,8 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for Bucket source reconciliation")
|
logger.Waitingf("waiting for Bucket source reconciliation")
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isBucketReady(ctx, kubeClient, namespacedName, bucket)); err != nil {
|
isBucketReady(kubeClient, namespacedName, bucket)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("Bucket source reconciliation completed")
|
logger.Successf("Bucket source reconciliation completed")
|
||||||
@@ -248,9 +248,8 @@ func upsertBucket(ctx context.Context, kubeClient client.Client,
|
|||||||
return namespacedName, nil
|
return namespacedName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isBucketReady(ctx context.Context, kubeClient client.Client,
|
func isBucketReady(kubeClient client.Client, namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionWithContextFunc {
|
||||||
namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionFunc {
|
return func(ctx context.Context) (bool, error) {
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, bucket)
|
err := kubeClient.Get(ctx, namespacedName, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -325,8 +325,8 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for GitRepository source reconciliation")
|
logger.Waitingf("waiting for GitRepository source reconciliation")
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isGitRepositoryReady(ctx, kubeClient, namespacedName, &gitRepository)); err != nil {
|
isGitRepositoryReady(kubeClient, namespacedName, &gitRepository)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("GitRepository source reconciliation completed")
|
logger.Successf("GitRepository source reconciliation completed")
|
||||||
@@ -369,9 +369,8 @@ func upsertGitRepository(ctx context.Context, kubeClient client.Client,
|
|||||||
return namespacedName, nil
|
return namespacedName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isGitRepositoryReady(ctx context.Context, kubeClient client.Client,
|
func isGitRepositoryReady(kubeClient client.Client, namespacedName types.NamespacedName, gitRepository *sourcev1.GitRepository) wait.ConditionWithContextFunc {
|
||||||
namespacedName types.NamespacedName, gitRepository *sourcev1.GitRepository) wait.ConditionFunc {
|
return func(ctx context.Context) (bool, error) {
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, gitRepository)
|
err := kubeClient.Get(ctx, namespacedName, gitRepository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -231,8 +231,8 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for HelmRepository source reconciliation")
|
logger.Waitingf("waiting for HelmRepository source reconciliation")
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isHelmRepositoryReady(ctx, kubeClient, namespacedName, helmRepository)); err != nil {
|
isHelmRepositoryReady(kubeClient, namespacedName, helmRepository)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("HelmRepository source reconciliation completed")
|
logger.Successf("HelmRepository source reconciliation completed")
|
||||||
@@ -280,9 +280,8 @@ func upsertHelmRepository(ctx context.Context, kubeClient client.Client,
|
|||||||
return namespacedName, nil
|
return namespacedName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isHelmRepositoryReady(ctx context.Context, kubeClient client.Client,
|
func isHelmRepositoryReady(kubeClient client.Client, namespacedName types.NamespacedName, helmRepository *sourcev1.HelmRepository) wait.ConditionWithContextFunc {
|
||||||
namespacedName types.NamespacedName, helmRepository *sourcev1.HelmRepository) wait.ConditionFunc {
|
return func(ctx context.Context) (bool, error) {
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, helmRepository)
|
err := kubeClient.Get(ctx, namespacedName, helmRepository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -51,16 +51,18 @@ var createSourceOCIRepositoryCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type sourceOCIRepositoryFlags struct {
|
type sourceOCIRepositoryFlags struct {
|
||||||
url string
|
url string
|
||||||
tag string
|
tag string
|
||||||
semver string
|
semver string
|
||||||
digest string
|
digest string
|
||||||
secretRef string
|
secretRef string
|
||||||
serviceAccount string
|
serviceAccount string
|
||||||
certSecretRef string
|
certSecretRef string
|
||||||
ignorePaths []string
|
verifyProvider flags.SourceOCIVerifyProvider
|
||||||
provider flags.SourceOCIProvider
|
verifySecretRef string
|
||||||
insecure bool
|
ignorePaths []string
|
||||||
|
provider flags.SourceOCIProvider
|
||||||
|
insecure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var sourceOCIRepositoryArgs = newSourceOCIFlags()
|
var sourceOCIRepositoryArgs = newSourceOCIFlags()
|
||||||
@@ -80,6 +82,8 @@ func init() {
|
|||||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')")
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')")
|
||||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.serviceAccount, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret")
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.serviceAccount, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret")
|
||||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates")
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates")
|
||||||
|
createSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.verifyProvider, "verify-provider", sourceOCIRepositoryArgs.verifyProvider.Description())
|
||||||
|
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifySecretRef, "verify-secret-ref", "", "the name of a secret to use for signature verification")
|
||||||
createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)")
|
createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)")
|
||||||
createSourceOCIRepositoryCmd.Flags().BoolVar(&sourceOCIRepositoryArgs.insecure, "insecure", false, "for when connecting to a non-TLS registries over plain HTTP")
|
createSourceOCIRepositoryCmd.Flags().BoolVar(&sourceOCIRepositoryArgs.insecure, "insecure", false, "for when connecting to a non-TLS registries over plain HTTP")
|
||||||
|
|
||||||
@@ -156,6 +160,19 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider := sourceOCIRepositoryArgs.verifyProvider.String(); provider != "" {
|
||||||
|
repository.Spec.Verify = &sourcev1.OCIRepositoryVerification{
|
||||||
|
Provider: provider,
|
||||||
|
}
|
||||||
|
if secretName := sourceOCIRepositoryArgs.verifySecretRef; secretName != "" {
|
||||||
|
repository.Spec.Verify.SecretRef = &meta.LocalObjectReference{
|
||||||
|
Name: secretName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if sourceOCIRepositoryArgs.verifySecretRef != "" {
|
||||||
|
return fmt.Errorf("a verification provider must be specified when a secret is specified")
|
||||||
|
}
|
||||||
|
|
||||||
if createArgs.export {
|
if createArgs.export {
|
||||||
return printExport(exportOCIRepository(repository))
|
return printExport(exportOCIRepository(repository))
|
||||||
}
|
}
|
||||||
@@ -175,8 +192,8 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Waitingf("waiting for OCIRepository reconciliation")
|
logger.Waitingf("waiting for OCIRepository reconciliation")
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isOCIRepositoryReady(ctx, kubeClient, namespacedName, repository)); err != nil {
|
isOCIRepositoryReady(kubeClient, namespacedName, repository)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("OCIRepository reconciliation completed")
|
logger.Successf("OCIRepository reconciliation completed")
|
||||||
@@ -219,9 +236,8 @@ func upsertOCIRepository(ctx context.Context, kubeClient client.Client,
|
|||||||
return namespacedName, nil
|
return namespacedName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isOCIRepositoryReady(ctx context.Context, kubeClient client.Client,
|
func isOCIRepositoryReady(kubeClient client.Client, namespacedName types.NamespacedName, ociRepository *sourcev1.OCIRepository) wait.ConditionWithContextFunc {
|
||||||
namespacedName types.NamespacedName, ociRepository *sourcev1.OCIRepository) wait.ConditionFunc {
|
return func(ctx context.Context) (bool, error) {
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, ociRepository)
|
err := kubeClient.Get(ctx, namespacedName, ociRepository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ func TestCreateSourceOCI(t *testing.T) {
|
|||||||
args: "create source oci podinfo",
|
args: "create source oci podinfo",
|
||||||
assertFunc: assertError("url is required"),
|
assertFunc: assertError("url is required"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "verify provider not specified",
|
||||||
|
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --verify-secret-ref=cosign-pub",
|
||||||
|
assertFunc: assertError("a verification provider must be specified when a secret is specified"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "export manifest",
|
name: "export manifest",
|
||||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --export",
|
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --export",
|
||||||
@@ -46,6 +51,11 @@ func TestCreateSourceOCI(t *testing.T) {
|
|||||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --secret-ref=creds --export",
|
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --secret-ref=creds --export",
|
||||||
assertFunc: assertGoldenFile("./testdata/oci/export_with_secret.golden"),
|
assertFunc: assertGoldenFile("./testdata/oci/export_with_secret.golden"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "export manifest with verify secret",
|
||||||
|
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --verify-provider=cosign --verify-secret-ref=cosign-pub --export",
|
||||||
|
assertFunc: assertGoldenFile("./testdata/oci/export_with_verify_secret.golden"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -91,6 +91,12 @@ func TestDiffKustomization(t *testing.T) {
|
|||||||
objectFile: "./testdata/diff-kustomization/stringdata-sops-secret.yaml",
|
objectFile: "./testdata/diff-kustomization/stringdata-sops-secret.yaml",
|
||||||
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-stringdata-sops-secret.golden"),
|
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-stringdata-sops-secret.golden"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "diff where kustomization file has multiple objects with the same name",
|
||||||
|
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false --kustomization-file ./testdata/diff-kustomization/flux-kustomization-multiobj.yaml",
|
||||||
|
objectFile: "",
|
||||||
|
assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl := map[string]string{
|
tmpl := map[string]string{
|
||||||
|
|||||||
@@ -62,8 +62,14 @@ var eventsCmd = &cobra.Command{
|
|||||||
# Display events for flux resources in all namespaces
|
# Display events for flux resources in all namespaces
|
||||||
flux events -A
|
flux events -A
|
||||||
|
|
||||||
# Display events for flux resources
|
# Display events for a Kustomization named podinfo
|
||||||
flux events --for Kustomization/podinfo
|
flux events --for Kustomization/podinfo
|
||||||
|
|
||||||
|
# Display events for all Kustomizations in default namespace
|
||||||
|
flux events --for Kustomization -n default
|
||||||
|
|
||||||
|
# Display warning events for alert resources
|
||||||
|
flux events --for Alert/podinfo --types warning
|
||||||
`,
|
`,
|
||||||
RunE: eventsCmdRun,
|
RunE: eventsCmdRun,
|
||||||
}
|
}
|
||||||
@@ -84,7 +90,7 @@ func init() {
|
|||||||
"indicate if the events should be streamed")
|
"indicate if the events should be streamed")
|
||||||
eventsCmd.Flags().StringVar(&eventArgs.forSelector, "for", "",
|
eventsCmd.Flags().StringVar(&eventArgs.forSelector, "for", "",
|
||||||
"get events for a particular object")
|
"get events for a particular object")
|
||||||
eventsCmd.Flags().StringSliceVar(&eventArgs.filterTypes, "types", []string{}, "filter events for certain types")
|
eventsCmd.Flags().StringSliceVar(&eventArgs.filterTypes, "types", []string{}, "filter events for certain types (valid types are: Normal, Warning)")
|
||||||
rootCmd.AddCommand(eventsCmd)
|
rootCmd.AddCommand(eventsCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +98,10 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
if err := validateEventTypes(eventArgs.filterTypes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
kubeclient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
kubeclient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -103,21 +113,33 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var diffRefNs bool
|
var diffRefNs bool
|
||||||
clientListOpts := getListOpt(namespace, eventArgs.forSelector)
|
clientListOpts := []client.ListOption{client.InNamespace(*kubeconfigArgs.Namespace)}
|
||||||
var refListOpts [][]client.ListOption
|
var refListOpts [][]client.ListOption
|
||||||
if eventArgs.forSelector != "" {
|
if eventArgs.forSelector != "" {
|
||||||
refs, err := getObjectRef(ctx, kubeclient, eventArgs.forSelector, *kubeconfigArgs.Namespace)
|
kind, name := getKindNameFromSelector(eventArgs.forSelector)
|
||||||
|
if kind == "" {
|
||||||
|
return fmt.Errorf("--for selector must be of format <kind>[/<name>]")
|
||||||
|
}
|
||||||
|
|
||||||
|
refInfoKind, err := fluxKindMap.getRefInfo(kind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
clientListOpts = append(clientListOpts, getListOpt(refInfoKind.gvk.Kind, name))
|
||||||
for _, ref := range refs {
|
if name != "" {
|
||||||
kind, name, refNs := utils.ParseObjectKindNameNamespace(ref)
|
refs, err := getObjectRef(ctx, kubeclient, refInfoKind, name, *kubeconfigArgs.Namespace)
|
||||||
if refNs != namespace {
|
if err != nil {
|
||||||
diffRefNs = true
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ref := range refs {
|
||||||
|
refKind, refName, refNs := utils.ParseObjectKindNameNamespace(ref)
|
||||||
|
if refNs != namespace {
|
||||||
|
diffRefNs = true
|
||||||
|
}
|
||||||
|
refOpt := []client.ListOption{getListOpt(refKind, refName), client.InNamespace(refNs)}
|
||||||
|
refListOpts = append(refListOpts, refOpt)
|
||||||
}
|
}
|
||||||
refSelector := fmt.Sprintf("%s/%s", kind, name)
|
|
||||||
refListOpts = append(refListOpts, getListOpt(refNs, refSelector))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +149,9 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rows, err := getRows(ctx, kubeclient, clientListOpts, refListOpts, showNamespace)
|
rows, err := getRows(ctx, kubeclient, clientListOpts, refListOpts, showNamespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if len(rows) == 0 {
|
if len(rows) == 0 {
|
||||||
if eventArgs.allNamespaces {
|
if eventArgs.allNamespaces {
|
||||||
logger.Failuref("No events found.")
|
logger.Failuref("No events found.")
|
||||||
@@ -137,8 +162,7 @@ func eventsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
headers := getHeaders(showNamespace)
|
headers := getHeaders(showNamespace)
|
||||||
err = printers.TablePrinter(headers).Print(cmd.OutOrStdout(), rows)
|
return printers.TablePrinter(headers).Print(cmd.OutOrStdout(), rows)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRows(ctx context.Context, kubeclient client.Client, clientListOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) ([][]string, error) {
|
func getRows(ctx context.Context, kubeclient client.Client, clientListOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) ([][]string, error) {
|
||||||
@@ -168,11 +192,11 @@ func getRows(ctx context.Context, kubeclient client.Client, clientListOpts []cli
|
|||||||
|
|
||||||
func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.EventList, clientListOpts []client.ListOption) error {
|
func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.EventList, clientListOpts []client.ListOption) error {
|
||||||
listOpts := &metav1.ListOptions{}
|
listOpts := &metav1.ListOptions{}
|
||||||
|
clientListOpts = append(clientListOpts, client.Limit(cmdutil.DefaultChunkSize))
|
||||||
err := runtimeresource.FollowContinue(listOpts,
|
err := runtimeresource.FollowContinue(listOpts,
|
||||||
func(options metav1.ListOptions) (runtime.Object, error) {
|
func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
newEvents := &corev1.EventList{}
|
newEvents := &corev1.EventList{}
|
||||||
err := kubeclient.List(ctx, newEvents, clientListOpts...)
|
if err := kubeclient.List(ctx, newEvents, clientListOpts...); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting events: %w", err)
|
return nil, fmt.Errorf("error getting events: %w", err)
|
||||||
}
|
}
|
||||||
el.Items = append(el.Items, newEvents.Items...)
|
el.Items = append(el.Items, newEvents.Items...)
|
||||||
@@ -182,21 +206,22 @@ func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.E
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getListOpt(namespace, selector string) []client.ListOption {
|
func getListOpt(kind, name string) client.ListOption {
|
||||||
clientListOpts := []client.ListOption{client.Limit(cmdutil.DefaultChunkSize), client.InNamespace(namespace)}
|
var sel fields.Selector
|
||||||
if selector != "" {
|
if name == "" {
|
||||||
kind, name := utils.ParseObjectKindName(selector)
|
sel = fields.OneTermEqualSelector("involvedObject.kind", kind)
|
||||||
sel := fields.AndSelectors(
|
} else {
|
||||||
|
sel = fields.AndSelectors(
|
||||||
fields.OneTermEqualSelector("involvedObject.kind", kind),
|
fields.OneTermEqualSelector("involvedObject.kind", kind),
|
||||||
fields.OneTermEqualSelector("involvedObject.name", name))
|
fields.OneTermEqualSelector("involvedObject.name", name))
|
||||||
clientListOpts = append(clientListOpts, client.MatchingFieldsSelector{Selector: sel})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return clientListOpts
|
return client.MatchingFieldsSelector{Selector: sel}
|
||||||
}
|
}
|
||||||
|
|
||||||
func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) error {
|
func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) error {
|
||||||
event := &corev1.EventList{}
|
event := &corev1.EventList{}
|
||||||
|
listOpts = append(listOpts, client.Limit(cmdutil.DefaultChunkSize))
|
||||||
eventWatch, err := kubeclient.Watch(ctx, event, listOpts...)
|
eventWatch, err := kubeclient.Watch(ctx, event, listOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -222,12 +247,7 @@ func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpt
|
|||||||
hdr = getHeaders(showNs)
|
hdr = getHeaders(showNs)
|
||||||
firstIteration = false
|
firstIteration = false
|
||||||
}
|
}
|
||||||
err = printers.TablePrinter(hdr).Print(os.Stdout, [][]string{rows})
|
return printers.TablePrinter(hdr).Print(os.Stdout, [][]string{rows})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, refOpts := range refListOpts {
|
for _, refOpts := range refListOpts {
|
||||||
@@ -236,8 +256,7 @@ func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpt
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
err := receiveEventChan(ctx, refEventWatch, handleEvent)
|
if err := receiveEventChan(ctx, refEventWatch, handleEvent); err != nil {
|
||||||
if err != nil {
|
|
||||||
logger.Failuref("error watching events: %s", err.Error())
|
logger.Failuref("error watching events: %s", err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -286,13 +305,7 @@ func getEventRow(e corev1.Event, showNs bool) []string {
|
|||||||
// getObjectRef is used to get the metadata of a resource that the selector(in the format <kind/name>) references.
|
// getObjectRef is used to get the metadata of a resource that the selector(in the format <kind/name>) references.
|
||||||
// It returns an empty string if the resource doesn't reference any resource
|
// It returns an empty string if the resource doesn't reference any resource
|
||||||
// and a string with the format `<kind>/<name>.<namespace>` if it does.
|
// and a string with the format `<kind>/<name>.<namespace>` if it does.
|
||||||
func getObjectRef(ctx context.Context, kubeclient client.Client, selector string, ns string) ([]string, error) {
|
func getObjectRef(ctx context.Context, kubeclient client.Client, ref refInfo, name, ns string) ([]string, error) {
|
||||||
kind, name := utils.ParseObjectKindName(selector)
|
|
||||||
ref, err := fluxKindMap.getRefInfo(kind)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting groupversion: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// the resource has no source ref
|
// the resource has no source ref
|
||||||
if len(ref.field) == 0 {
|
if len(ref.field) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -300,31 +313,30 @@ func getObjectRef(ctx context.Context, kubeclient client.Client, selector string
|
|||||||
|
|
||||||
obj := &unstructured.Unstructured{}
|
obj := &unstructured.Unstructured{}
|
||||||
obj.SetGroupVersionKind(schema.GroupVersionKind{
|
obj.SetGroupVersionKind(schema.GroupVersionKind{
|
||||||
Kind: kind,
|
Kind: ref.gvk.Kind,
|
||||||
Version: ref.gv.Version,
|
Version: ref.gvk.Version,
|
||||||
Group: ref.gv.Group,
|
Group: ref.gvk.Group,
|
||||||
})
|
})
|
||||||
objName := types.NamespacedName{
|
objName := types.NamespacedName{
|
||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
Name: name,
|
Name: name,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = kubeclient.Get(ctx, objName, obj)
|
if err := kubeclient.Get(ctx, objName, obj); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
|
||||||
refKind := ref.kind
|
refKind := ref.kind
|
||||||
if refKind == "" {
|
if refKind == "" {
|
||||||
kindField := append(ref.field, "kind")
|
kindField := append(ref.field, "kind")
|
||||||
refKind, ok, err = unstructured.NestedString(obj.Object, kindField...)
|
specKind, ok, err := unstructured.NestedString(obj.Object, kindField...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("field '%s' for '%s' not found", strings.Join(kindField, "."), objName)
|
return nil, fmt.Errorf("field '%s' for '%s' not found", strings.Join(kindField, "."), objName)
|
||||||
}
|
}
|
||||||
|
refKind = specKind
|
||||||
}
|
}
|
||||||
|
|
||||||
nameField := append(ref.field, "name")
|
nameField := append(ref.field, "name")
|
||||||
@@ -374,22 +386,40 @@ func (r refMap) hasKind(kind string) bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateEventTypes checks that the event types passed into the function
|
||||||
|
// is either equal to `Normal` or `Warning` which are currently the two supported types.
|
||||||
|
// https://github.com/kubernetes/kubernetes/blob/a8a1abc25cad87333840cd7d54be2efaf31a3177/staging/src/k8s.io/api/core/v1/types.go#L6212
|
||||||
|
func validateEventTypes(eventTypes []string) error {
|
||||||
|
for _, t := range eventTypes {
|
||||||
|
if !strings.EqualFold(corev1.EventTypeWarning, t) && !strings.EqualFold(corev1.EventTypeNormal, t) {
|
||||||
|
return fmt.Errorf("type '%s' not supported. Supported types are Normal, Warning", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type refInfo struct {
|
type refInfo struct {
|
||||||
gv schema.GroupVersion
|
// gvk is the group version kind of the resource
|
||||||
kind string
|
gvk schema.GroupVersionKind
|
||||||
|
// kind is the kind that the resource references if it's not static
|
||||||
|
kind string
|
||||||
|
// crossNamespaced indicates if this resource uses cross namespaced references
|
||||||
crossNamespaced bool
|
crossNamespaced bool
|
||||||
otherRefs func(namespace, name string) []string
|
// otherRefs returns other reference that might not be directly accessible
|
||||||
field []string
|
// from the spec of the object
|
||||||
|
otherRefs func(namespace, name string) []string
|
||||||
|
field []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var fluxKindMap = refMap{
|
var fluxKindMap = refMap{
|
||||||
kustomizev1.KustomizationKind: {
|
kustomizev1.KustomizationKind: {
|
||||||
gv: kustomizev1.GroupVersion,
|
gvk: kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind),
|
||||||
crossNamespaced: true,
|
crossNamespaced: true,
|
||||||
field: []string{"spec", "sourceRef"},
|
field: []string{"spec", "sourceRef"},
|
||||||
},
|
},
|
||||||
helmv2.HelmReleaseKind: {
|
helmv2.HelmReleaseKind: {
|
||||||
gv: helmv2.GroupVersion,
|
gvk: helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind),
|
||||||
crossNamespaced: true,
|
crossNamespaced: true,
|
||||||
otherRefs: func(namespace, name string) []string {
|
otherRefs: func(namespace, name string) []string {
|
||||||
return []string{fmt.Sprintf("%s/%s-%s", sourcev1b2.HelmChartKind, namespace, name)}
|
return []string{fmt.Sprintf("%s/%s-%s", sourcev1b2.HelmChartKind, namespace, name)}
|
||||||
@@ -397,26 +427,30 @@ var fluxKindMap = refMap{
|
|||||||
field: []string{"spec", "chart", "spec", "sourceRef"},
|
field: []string{"spec", "chart", "spec", "sourceRef"},
|
||||||
},
|
},
|
||||||
notificationv1b2.AlertKind: {
|
notificationv1b2.AlertKind: {
|
||||||
gv: notificationv1b2.GroupVersion,
|
gvk: notificationv1b2.GroupVersion.WithKind(notificationv1b2.AlertKind),
|
||||||
kind: notificationv1b2.ProviderKind,
|
kind: notificationv1b2.ProviderKind,
|
||||||
crossNamespaced: false,
|
crossNamespaced: false,
|
||||||
field: []string{"spec", "providerRef"},
|
field: []string{"spec", "providerRef"},
|
||||||
},
|
},
|
||||||
notificationv1.ReceiverKind: {gv: notificationv1.GroupVersion},
|
notificationv1.ReceiverKind: {gvk: notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)},
|
||||||
notificationv1b2.ProviderKind: {gv: notificationv1b2.GroupVersion},
|
notificationv1b2.ProviderKind: {gvk: notificationv1b2.GroupVersion.WithKind(notificationv1b2.ProviderKind)},
|
||||||
imagev1.ImagePolicyKind: {
|
imagev1.ImagePolicyKind: {
|
||||||
gv: imagev1.GroupVersion,
|
gvk: imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind),
|
||||||
kind: imagev1.ImageRepositoryKind,
|
kind: imagev1.ImageRepositoryKind,
|
||||||
crossNamespaced: true,
|
crossNamespaced: true,
|
||||||
field: []string{"spec", "imageRepositoryRef"},
|
field: []string{"spec", "imageRepositoryRef"},
|
||||||
},
|
},
|
||||||
sourcev1.GitRepositoryKind: {gv: sourcev1.GroupVersion},
|
sourcev1b2.HelmChartKind: {
|
||||||
sourcev1b2.OCIRepositoryKind: {gv: sourcev1b2.GroupVersion},
|
gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.HelmChartKind),
|
||||||
sourcev1b2.BucketKind: {gv: sourcev1b2.GroupVersion},
|
crossNamespaced: true,
|
||||||
sourcev1b2.HelmRepositoryKind: {gv: sourcev1b2.GroupVersion},
|
field: []string{"spec", "sourceRef"},
|
||||||
sourcev1b2.HelmChartKind: {gv: sourcev1b2.GroupVersion},
|
},
|
||||||
autov1.ImageUpdateAutomationKind: {gv: autov1.GroupVersion},
|
sourcev1.GitRepositoryKind: {gvk: sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)},
|
||||||
imagev1.ImageRepositoryKind: {gv: imagev1.GroupVersion},
|
sourcev1b2.OCIRepositoryKind: {gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.OCIRepositoryKind)},
|
||||||
|
sourcev1b2.BucketKind: {gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.BucketKind)},
|
||||||
|
sourcev1b2.HelmRepositoryKind: {gvk: sourcev1b2.GroupVersion.WithKind(sourcev1b2.HelmRepositoryKind)},
|
||||||
|
autov1.ImageUpdateAutomationKind: {gvk: autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)},
|
||||||
|
imagev1.ImageRepositoryKind: {gvk: imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)},
|
||||||
}
|
}
|
||||||
|
|
||||||
func ignoreEvent(e corev1.Event) bool {
|
func ignoreEvent(e corev1.Event) bool {
|
||||||
@@ -434,7 +468,19 @@ func ignoreEvent(e corev1.Event) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// The functions below are copied from: https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/events/events.go#L347
|
func getKindNameFromSelector(selector string) (string, string) {
|
||||||
|
kind, name := utils.ParseObjectKindName(selector)
|
||||||
|
// if there's no slash in the selector utils.ParseObjectKindName returns the
|
||||||
|
// input string as the name but here we want it as the kind instead
|
||||||
|
if kind == "" && name != "" {
|
||||||
|
kind = name
|
||||||
|
name = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return kind, name
|
||||||
|
}
|
||||||
|
|
||||||
|
// The functions below are copied from: https://github.com/kubernetes/kubectl/blob/4ecd7bd0f0799f191335a331ca3c6a397a888233/pkg/cmd/events/events.go#L294
|
||||||
|
|
||||||
// SortableEvents implements sort.Interface for []api.Event by time
|
// SortableEvents implements sort.Interface for []api.Event by time
|
||||||
type SortableEvents []corev1.Event
|
type SortableEvents []corev1.Event
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||||
|
|
||||||
@@ -216,6 +215,12 @@ func Test_getObjectRef(t *testing.T) {
|
|||||||
namespace: "default",
|
namespace: "default",
|
||||||
want: []string{"ImageRepository/acr-podinfo.flux-system"},
|
want: []string{"ImageRepository/acr-podinfo.flux-system"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Source Ref for ImagePolicy (lowercased)",
|
||||||
|
selector: "imagepolicy/podinfo",
|
||||||
|
namespace: "default",
|
||||||
|
want: []string{"ImageRepository/acr-podinfo.flux-system"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Empty Ref for Provider",
|
name: "Empty Ref for Provider",
|
||||||
selector: "Provider/slack",
|
selector: "Provider/slack",
|
||||||
@@ -232,11 +237,13 @@ func Test_getObjectRef(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
got, err := getObjectRef(context.Background(), c, tt.selector, tt.namespace)
|
kind, name := getKindNameFromSelector(tt.selector)
|
||||||
|
infoRef, err := fluxKindMap.getRefInfo(kind)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
g.Expect(err).To(HaveOccurred())
|
g.Expect(err).To(HaveOccurred())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
got, err := getObjectRef(context.Background(), c, infoRef, name, tt.namespace)
|
||||||
|
|
||||||
g.Expect(err).To(Not(HaveOccurred()))
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
g.Expect(got).To(Equal(tt.want))
|
g.Expect(got).To(Equal(tt.want))
|
||||||
@@ -261,6 +268,7 @@ func Test_getRows(t *testing.T) {
|
|||||||
}
|
}
|
||||||
builder = builder.WithLists(eventList)
|
builder = builder.WithLists(eventList)
|
||||||
builder.WithIndex(&corev1.Event{}, "involvedObject.kind/name", kindNameIndexer)
|
builder.WithIndex(&corev1.Event{}, "involvedObject.kind/name", kindNameIndexer)
|
||||||
|
builder.WithIndex(&corev1.Event{}, "involvedObject.kind", kindIndexer)
|
||||||
c := builder.Build()
|
c := builder.Build()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -320,6 +328,16 @@ func Test_getRows(t *testing.T) {
|
|||||||
{"flux-system", "<unknown>", "info", "Info Reason", "GitRepository/flux-system", "Info Message"},
|
{"flux-system", "<unknown>", "info", "Info Reason", "GitRepository/flux-system", "Info Message"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "All Kustomization (lowercased selector)",
|
||||||
|
selector: "kustomization",
|
||||||
|
expected: [][]string{
|
||||||
|
{"default", "<unknown>", "error", "Error Reason", "Kustomization/podinfo", "Error Message"},
|
||||||
|
{"default", "<unknown>", "info", "Info Reason", "Kustomization/podinfo", "Info Message"},
|
||||||
|
{"flux-system", "<unknown>", "error", "Error Reason", "Kustomization/flux-system", "Error Message"},
|
||||||
|
{"flux-system", "<unknown>", "info", "Info Reason", "Kustomization/flux-system", "Info Message"},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "HelmRelease with crossnamespaced HelmRepository",
|
name: "HelmRelease with crossnamespaced HelmRepository",
|
||||||
selector: "HelmRelease/podinfo",
|
selector: "HelmRelease/podinfo",
|
||||||
@@ -333,6 +351,19 @@ func Test_getRows(t *testing.T) {
|
|||||||
{"flux-system", "<unknown>", "info", "Info Reason", "HelmChart/default-podinfo", "Info Message"},
|
{"flux-system", "<unknown>", "info", "Info Reason", "HelmChart/default-podinfo", "Info Message"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "HelmRelease with crossnamespaced HelmRepository (lowercased)",
|
||||||
|
selector: "helmrelease/podinfo",
|
||||||
|
namespace: "default",
|
||||||
|
expected: [][]string{
|
||||||
|
{"default", "<unknown>", "error", "Error Reason", "HelmRelease/podinfo", "Error Message"},
|
||||||
|
{"default", "<unknown>", "info", "Info Reason", "HelmRelease/podinfo", "Info Message"},
|
||||||
|
{"flux-system", "<unknown>", "error", "Error Reason", "HelmRepository/podinfo", "Error Message"},
|
||||||
|
{"flux-system", "<unknown>", "info", "Info Reason", "HelmRepository/podinfo", "Info Message"},
|
||||||
|
{"flux-system", "<unknown>", "error", "Error Reason", "HelmChart/default-podinfo", "Error Message"},
|
||||||
|
{"flux-system", "<unknown>", "info", "Info Reason", "HelmChart/default-podinfo", "Info Message"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -341,37 +372,42 @@ func Test_getRows(t *testing.T) {
|
|||||||
|
|
||||||
var refs []string
|
var refs []string
|
||||||
var refNs, refKind, refName string
|
var refNs, refKind, refName string
|
||||||
|
var clientOpts = []client.ListOption{client.InNamespace(tt.namespace)}
|
||||||
if tt.selector != "" {
|
if tt.selector != "" {
|
||||||
refs, err = getObjectRef(context.Background(), c, tt.selector, tt.namespace)
|
kind, name := getKindNameFromSelector(tt.selector)
|
||||||
g.Expect(err).To(Not(HaveOccurred()))
|
infoRef, err := fluxKindMap.getRefInfo(kind)
|
||||||
|
clientOpts = append(clientOpts, getTestListOpt(infoRef.gvk.Kind, name))
|
||||||
|
if name != "" {
|
||||||
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
|
refs, err = getObjectRef(context.Background(), c, infoRef, name, tt.namespace)
|
||||||
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.Expect(err).To(Not(HaveOccurred()))
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
|
|
||||||
clientOpts := getTestListOpt(tt.namespace, tt.selector)
|
|
||||||
var refOpts [][]client.ListOption
|
var refOpts [][]client.ListOption
|
||||||
for _, ref := range refs {
|
for _, ref := range refs {
|
||||||
refKind, refName, refNs = utils.ParseObjectKindNameNamespace(ref)
|
refKind, refName, refNs = utils.ParseObjectKindNameNamespace(ref)
|
||||||
refSelector := fmt.Sprintf("%s/%s", refKind, refName)
|
refOpts = append(refOpts, []client.ListOption{client.InNamespace(refNs), getTestListOpt(refKind, refName)})
|
||||||
refOpts = append(refOpts, getTestListOpt(refNs, refSelector))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showNs := tt.namespace == "" || (refNs != "" && refNs != tt.namespace)
|
showNs := tt.namespace == "" || (refNs != "" && refNs != tt.namespace)
|
||||||
rows, err := getRows(context.Background(), c, clientOpts, refOpts, showNs)
|
rows, err := getRows(context.Background(), c, clientOpts, refOpts, showNs)
|
||||||
g.Expect(err).To(Not(HaveOccurred()))
|
g.Expect(err).To(Not(HaveOccurred()))
|
||||||
g.Expect(rows).To(Equal(tt.expected))
|
g.Expect(rows).To(ConsistOf(tt.expected))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTestListOpt(namespace, selector string) []client.ListOption {
|
func getTestListOpt(kind, name string) client.ListOption {
|
||||||
clientListOpts := []client.ListOption{client.Limit(cmdutil.DefaultChunkSize), client.InNamespace(namespace)}
|
var sel fields.Selector
|
||||||
if selector != "" {
|
if name == "" {
|
||||||
sel := fields.OneTermEqualSelector("involvedObject.kind/name", selector)
|
sel = fields.OneTermEqualSelector("involvedObject.kind", kind)
|
||||||
clientListOpts = append(clientListOpts, client.MatchingFieldsSelector{Selector: sel})
|
} else {
|
||||||
|
sel = fields.OneTermEqualSelector("involvedObject.kind/name", fmt.Sprintf("%s/%s", kind, name))
|
||||||
}
|
}
|
||||||
|
return client.MatchingFieldsSelector{Selector: sel}
|
||||||
return clientListOpts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getScheme() *runtime.Scheme {
|
func getScheme() *runtime.Scheme {
|
||||||
@@ -393,7 +429,7 @@ func createEvent(obj client.Object, eventType, msg, reason string) corev1.Event
|
|||||||
return corev1.Event{
|
return corev1.Event{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: obj.GetNamespace(),
|
Namespace: obj.GetNamespace(),
|
||||||
// name of event needs to be unique so fak
|
// name of event needs to be unique
|
||||||
Name: obj.GetNamespace() + obj.GetNamespace() + obj.GetObjectKind().GroupVersionKind().Kind + eventType,
|
Name: obj.GetNamespace() + obj.GetNamespace() + obj.GetObjectKind().GroupVersionKind().Kind + eventType,
|
||||||
},
|
},
|
||||||
Reason: reason,
|
Reason: reason,
|
||||||
@@ -415,3 +451,12 @@ func kindNameIndexer(obj client.Object) []string {
|
|||||||
|
|
||||||
return []string{fmt.Sprintf("%s/%s", e.InvolvedObject.Kind, e.InvolvedObject.Name)}
|
return []string{fmt.Sprintf("%s/%s", e.InvolvedObject.Kind, e.InvolvedObject.Name)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func kindIndexer(obj client.Object) []string {
|
||||||
|
e, ok := obj.(*corev1.Event)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("Expected a Event, got %T", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{e.InvolvedObject.Kind}
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ type exportableWithSecretList interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type exportWithSecretCommand struct {
|
type exportWithSecretCommand struct {
|
||||||
apiType
|
|
||||||
object exportableWithSecret
|
object exportableWithSecret
|
||||||
list exportableWithSecretList
|
list exportableWithSecretList
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -27,6 +28,7 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
"k8s.io/client-go/discovery"
|
||||||
watchtools "k8s.io/client-go/tools/watch"
|
watchtools "k8s.io/client-go/tools/watch"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
@@ -176,6 +178,10 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
err = kubeClient.List(ctx, get.list.asClientList(), listOpts...)
|
err = kubeClient.List(ctx, get.list.asClientList(), listOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
var discErr *discovery.ErrGroupDiscoveryFailed
|
||||||
|
if getAll && (strings.Contains(err.Error(), "no matches for kind") || errors.As(err, &discErr)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
|
||||||
@@ -77,7 +78,8 @@ func init() {
|
|||||||
func (s alertListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
func (s alertListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||||
item := s.Items[i]
|
item := s.Items[i]
|
||||||
status, msg := statusAndMessage(item.Status.Conditions)
|
status, msg := statusAndMessage(item.Status.Conditions)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind), strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||||
|
cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s alertListAdapter) headers(includeNamespace bool) []string {
|
func (s alertListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var getHelmReleaseCmd = &cobra.Command{
|
var getHelmReleaseCmd = &cobra.Command{
|
||||||
@@ -75,7 +77,7 @@ func (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool, incl
|
|||||||
revision := item.Status.LastAppliedRevision
|
revision := item.Status.LastAppliedRevision
|
||||||
status, msg := statusAndMessage(item.Status.Conditions)
|
status, msg := statusAndMessage(item.Status.Conditions)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||||
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a helmReleaseListAdapter) headers(includeNamespace bool) []string {
|
func (a helmReleaseListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||||
@@ -57,9 +55,7 @@ var getImageAllCmd = &cobra.Command{
|
|||||||
|
|
||||||
for _, c := range allImageCmd {
|
for _, c := range allImageCmd {
|
||||||
if err := c.run(cmd, args); err != nil {
|
if err := c.run(cmd, args); err != nil {
|
||||||
if !strings.Contains(err.Error(), "no matches for kind") {
|
logger.Failuref(err.Error())
|
||||||
logger.Failuref(err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||||
@@ -82,7 +83,7 @@ func (s imageRepositoryListAdapter) summariseItem(i int, includeNamespace bool,
|
|||||||
lastScan = item.Status.LastScanResult.ScanTime.Time.Format(time.RFC3339)
|
lastScan = item.Status.LastScanResult.ScanTime.Time.Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||||
lastScan, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
lastScan, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s imageRepositoryListAdapter) headers(includeNamespace bool) []string {
|
func (s imageRepositoryListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||||
@@ -81,7 +82,8 @@ func (s imageUpdateAutomationListAdapter) summariseItem(i int, includeNamespace
|
|||||||
if item.Status.LastAutomationRunTime != nil {
|
if item.Status.LastAutomationRunTime != nil {
|
||||||
lastRun = item.Status.LastAutomationRunTime.Time.Format(time.RFC3339)
|
lastRun = item.Status.LastAutomationRunTime.Time.Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind), lastRun, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
return append(nameColumns(&item, includeNamespace, includeKind), lastRun,
|
||||||
|
cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s imageUpdateAutomationListAdapter) headers(includeNamespace bool) []string {
|
func (s imageUpdateAutomationListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||||
@@ -83,7 +84,7 @@ func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool, in
|
|||||||
revision = utils.TruncateHex(revision)
|
revision = utils.TruncateHex(revision)
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||||
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a kustomizationListAdapter) headers(includeNamespace bool) []string {
|
func (a kustomizationListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||||
@@ -74,7 +75,8 @@ func init() {
|
|||||||
func (s receiverListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
func (s receiverListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||||
item := s.Items[i]
|
item := s.Items[i]
|
||||||
status, msg := statusAndMessage(item.Status.Conditions)
|
status, msg := statusAndMessage(item.Status.Conditions)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind), strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||||
|
cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s receiverListAdapter) headers(includeNamespace bool) []string {
|
func (s receiverListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
@@ -85,7 +86,7 @@ func (a *bucketListAdapter) summariseItem(i int, includeNamespace bool, includeK
|
|||||||
revision = utils.TruncateHex(revision)
|
revision = utils.TruncateHex(revision)
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||||
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a bucketListAdapter) headers(includeNamespace bool) []string {
|
func (a bucketListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
@@ -86,7 +87,7 @@ func (a *helmChartListAdapter) summariseItem(i int, includeNamespace bool, inclu
|
|||||||
// Message may still contain reference of e.g. commit chart was build from
|
// Message may still contain reference of e.g. commit chart was build from
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||||
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a helmChartListAdapter) headers(includeNamespace bool) []string {
|
func (a helmChartListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||||
@@ -85,7 +86,7 @@ func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i
|
|||||||
revision = utils.TruncateHex(revision)
|
revision = utils.TruncateHex(revision)
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||||
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a gitRepositoryListAdapter) headers(includeNamespace bool) []string {
|
func (a gitRepositoryListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
@@ -85,7 +86,7 @@ func (a *helmRepositoryListAdapter) summariseItem(i int, includeNamespace bool,
|
|||||||
revision = utils.TruncateHex(revision)
|
revision = utils.TruncateHex(revision)
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||||
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a helmRepositoryListAdapter) headers(includeNamespace bool) []string {
|
func (a helmRepositoryListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
@@ -85,7 +86,7 @@ func (a *ociRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i
|
|||||||
revision = utils.TruncateHex(revision)
|
revision = utils.TruncateHex(revision)
|
||||||
msg = utils.TruncateHex(msg)
|
msg = utils.TruncateHex(msg)
|
||||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||||
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
revision, cases.Title(language.English).String(strconv.FormatBool(item.Spec.Suspend)), status, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ociRepositoryListAdapter) headers(includeNamespace bool) []string {
|
func (a ociRepositoryListAdapter) headers(includeNamespace bool) []string {
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/v2/internal/flags"
|
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
@@ -34,6 +36,7 @@ import (
|
|||||||
|
|
||||||
var installCmd = &cobra.Command{
|
var installCmd = &cobra.Command{
|
||||||
Use: "install",
|
Use: "install",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
Short: "Install or upgrade Flux",
|
Short: "Install or upgrade Flux",
|
||||||
Long: `The install command deploys Flux in the specified namespace.
|
Long: `The install command deploys Flux in the specified namespace.
|
||||||
If a previous version is installed, then an in-place upgrade will be performed.`,
|
If a previous version is installed, then an in-place upgrade will be performed.`,
|
||||||
@@ -72,6 +75,7 @@ type installFlags struct {
|
|||||||
tokenAuth bool
|
tokenAuth bool
|
||||||
clusterDomain string
|
clusterDomain string
|
||||||
tolerationKeys []string
|
tolerationKeys []string
|
||||||
|
force bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var installArgs = NewInstallFlags()
|
var installArgs = NewInstallFlags()
|
||||||
@@ -98,6 +102,7 @@ func init() {
|
|||||||
installCmd.Flags().StringVar(&installArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
|
installCmd.Flags().StringVar(&installArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
|
||||||
installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil,
|
installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil,
|
||||||
"list of toleration keys used to schedule the components pods onto nodes with matching taints")
|
"list of toleration keys used to schedule the components pods onto nodes with matching taints")
|
||||||
|
installCmd.Flags().BoolVar(&installArgs.force, "force", false, "override existing Flux installation if it's managed by a diffrent tool such as Helm")
|
||||||
installCmd.Flags().MarkHidden("manifests")
|
installCmd.Flags().MarkHidden("manifests")
|
||||||
|
|
||||||
rootCmd.AddCommand(installCmd)
|
rootCmd.AddCommand(installCmd)
|
||||||
@@ -183,6 +188,35 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Successf("manifests build completed")
|
logger.Successf("manifests build completed")
|
||||||
logger.Actionf("installing components in %s namespace", *kubeconfigArgs.Namespace)
|
logger.Actionf("installing components in %s namespace", *kubeconfigArgs.Namespace)
|
||||||
|
|
||||||
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
installed := true
|
||||||
|
info, err := getFluxClusterInfo(ctx, kubeClient)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.IsNotFound(err) {
|
||||||
|
return fmt.Errorf("cluster info unavailable: %w", err)
|
||||||
|
}
|
||||||
|
installed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.bootstrapped {
|
||||||
|
return fmt.Errorf("this cluster has already been bootstrapped with Flux %s! Please use 'flux bootstrap' to upgrade",
|
||||||
|
info.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if installed && !installArgs.force {
|
||||||
|
err := confirmFluxInstallOverride(info)
|
||||||
|
if err != nil {
|
||||||
|
if err == promptui.ErrAbort {
|
||||||
|
return fmt.Errorf("installation cancelled")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))
|
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("install failed: %w", err)
|
return fmt.Errorf("install failed: %w", err)
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ func TestInstall(t *testing.T) {
|
|||||||
args: "install --namespace='@#[]'",
|
args: "install --namespace='@#[]'",
|
||||||
assert: assertError("namespace must be a valid DNS label: \"@#[]\""),
|
assert: assertError("namespace must be a valid DNS label: \"@#[]\""),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "invalid sub-command",
|
||||||
|
args: "install unexpectedPosArg --namespace=example",
|
||||||
|
assert: assertError(`unknown command "unexpectedPosArg" for "flux install"`),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -61,11 +61,26 @@ func TestKustomizationFromGit(t *testing.T) {
|
|||||||
"testdata/kustomization/suspend_kustomization_from_git.golden",
|
"testdata/kustomization/suspend_kustomization_from_git.golden",
|
||||||
tmpl,
|
tmpl,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"suspend kustomization tkfg foo tkfg bar",
|
||||||
|
"testdata/kustomization/suspend_kustomization_from_git_multiple_args.golden",
|
||||||
|
tmpl,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resume kustomization tkfg foo --wait",
|
||||||
|
"testdata/kustomization/resume_kustomization_from_git_multiple_args_wait.golden",
|
||||||
|
tmpl,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resume kustomization tkfg",
|
"resume kustomization tkfg",
|
||||||
"testdata/kustomization/resume_kustomization_from_git.golden",
|
"testdata/kustomization/resume_kustomization_from_git.golden",
|
||||||
tmpl,
|
tmpl,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resume kustomization tkfg tkfg",
|
||||||
|
"testdata/kustomization/resume_kustomization_from_git_multiple_args.golden",
|
||||||
|
tmpl,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"delete kustomization tkfg --silent",
|
"delete kustomization tkfg --silent",
|
||||||
"testdata/kustomization/delete_kustomization_from_git.golden",
|
"testdata/kustomization/delete_kustomization_from_git.golden",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -73,10 +74,10 @@ type logsFlags struct {
|
|||||||
fluxNamespace string
|
fluxNamespace string
|
||||||
allNamespaces bool
|
allNamespaces bool
|
||||||
sinceTime string
|
sinceTime string
|
||||||
sinceSeconds time.Duration
|
sinceDuration time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
var logsArgs = &logsFlags{
|
var logsArgs = logsFlags{
|
||||||
tail: -1,
|
tail: -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ func init() {
|
|||||||
logsCmd.Flags().Int64VarP(&logsArgs.tail, "tail", "", logsArgs.tail, "lines of recent log file to display")
|
logsCmd.Flags().Int64VarP(&logsArgs.tail, "tail", "", logsArgs.tail, "lines of recent log file to display")
|
||||||
logsCmd.Flags().StringVarP(&logsArgs.fluxNamespace, "flux-namespace", "", rootArgs.defaults.Namespace, "the namespace where the Flux components are running")
|
logsCmd.Flags().StringVarP(&logsArgs.fluxNamespace, "flux-namespace", "", rootArgs.defaults.Namespace, "the namespace where the Flux components are running")
|
||||||
logsCmd.Flags().BoolVarP(&logsArgs.allNamespaces, "all-namespaces", "A", false, "displays logs for objects across all namespaces")
|
logsCmd.Flags().BoolVarP(&logsArgs.allNamespaces, "all-namespaces", "A", false, "displays logs for objects across all namespaces")
|
||||||
logsCmd.Flags().DurationVar(&logsArgs.sinceSeconds, "since", logsArgs.sinceSeconds, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
|
logsCmd.Flags().DurationVar(&logsArgs.sinceDuration, "since", logsArgs.sinceDuration, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
|
||||||
logsCmd.Flags().StringVar(&logsArgs.sinceTime, "since-time", logsArgs.sinceTime, "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.")
|
logsCmd.Flags().StringVar(&logsArgs.sinceTime, "since-time", logsArgs.sinceTime, "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.")
|
||||||
rootCmd.AddCommand(logsCmd)
|
rootCmd.AddCommand(logsCmd)
|
||||||
}
|
}
|
||||||
@@ -115,7 +116,7 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("no argument required")
|
return fmt.Errorf("no argument required")
|
||||||
}
|
}
|
||||||
|
|
||||||
pods, err := getPods(ctx, clientset, fluxSelector)
|
pods, err := getPods(ctx, clientset, logsArgs.fluxNamespace, fluxSelector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -128,8 +129,8 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logOpts.TailLines = &logsArgs.tail
|
logOpts.TailLines = &logsArgs.tail
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(logsArgs.sinceTime) > 0 && logsArgs.sinceSeconds != 0 {
|
if len(logsArgs.sinceTime) > 0 && logsArgs.sinceDuration != 0 {
|
||||||
return fmt.Errorf("at most one of `sinceTime` or `sinceSeconds` may be specified")
|
return fmt.Errorf("at most one of `sinceTime` or `sinceDuration` may be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(logsArgs.sinceTime) > 0 {
|
if len(logsArgs.sinceTime) > 0 {
|
||||||
@@ -140,9 +141,9 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logOpts.SinceTime = &t
|
logOpts.SinceTime = &t
|
||||||
}
|
}
|
||||||
|
|
||||||
if logsArgs.sinceSeconds != 0 {
|
if logsArgs.sinceDuration != 0 {
|
||||||
// round up to the nearest second
|
// round up to the nearest second
|
||||||
sec := int64(logsArgs.sinceSeconds.Round(time.Second).Seconds())
|
sec := int64(logsArgs.sinceDuration.Round(time.Second).Seconds())
|
||||||
logOpts.SinceSeconds = &sec
|
logOpts.SinceSeconds = &sec
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,13 +164,16 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return podLogs(ctx, requests)
|
return podLogs(ctx, requests)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPods(ctx context.Context, c *kubernetes.Clientset, label string) ([]corev1.Pod, error) {
|
// getPods searches for all Deployments in the given namespace that match the given label and returns a list of Pods
|
||||||
|
// from these Deployments. For each Deployment a single Pod is chosen (based on various factors such as the running
|
||||||
|
// state). If no Pod is found, an error is returned.
|
||||||
|
func getPods(ctx context.Context, c *kubernetes.Clientset, ns string, label string) ([]corev1.Pod, error) {
|
||||||
var ret []corev1.Pod
|
var ret []corev1.Pod
|
||||||
|
|
||||||
opts := metav1.ListOptions{
|
opts := metav1.ListOptions{
|
||||||
LabelSelector: label,
|
LabelSelector: label,
|
||||||
}
|
}
|
||||||
deployList, err := c.AppsV1().Deployments(logsArgs.fluxNamespace).List(ctx, opts)
|
deployList, err := c.AppsV1().Deployments(ns).List(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
@@ -179,7 +183,7 @@ func getPods(ctx context.Context, c *kubernetes.Clientset, label string) ([]core
|
|||||||
opts := metav1.ListOptions{
|
opts := metav1.ListOptions{
|
||||||
LabelSelector: createLabelStringFromMap(label),
|
LabelSelector: createLabelStringFromMap(label),
|
||||||
}
|
}
|
||||||
podList, err := c.CoreV1().Pods(logsArgs.fluxNamespace).List(ctx, opts)
|
podList, err := c.CoreV1().Pods(ns).List(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
@@ -196,11 +200,16 @@ func getPods(ctx context.Context, c *kubernetes.Clientset, label string) ([]core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
return nil, fmt.Errorf("no Flux pods found in namespace %q", ns)
|
||||||
|
}
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parallelPodLogs(ctx context.Context, requests []rest.ResponseWrapper) error {
|
func parallelPodLogs(ctx context.Context, requests []rest.ResponseWrapper) error {
|
||||||
reader, writer := io.Pipe()
|
reader, writer := io.Pipe()
|
||||||
|
errReader, errWriter := io.Pipe()
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
wg.Add(len(requests))
|
wg.Add(len(requests))
|
||||||
|
|
||||||
@@ -208,7 +217,7 @@ func parallelPodLogs(ctx context.Context, requests []rest.ResponseWrapper) error
|
|||||||
go func(req rest.ResponseWrapper) {
|
go func(req rest.ResponseWrapper) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := logRequest(ctx, req, writer); err != nil {
|
if err := logRequest(ctx, req, writer); err != nil {
|
||||||
writer.CloseWithError(err)
|
fmt.Fprintf(errWriter, "failed getting logs: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}(request)
|
}(request)
|
||||||
@@ -217,20 +226,40 @@ func parallelPodLogs(ctx context.Context, requests []rest.ResponseWrapper) error
|
|||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
writer.Close()
|
writer.Close()
|
||||||
|
errWriter.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
_, err := io.Copy(os.Stdout, reader)
|
stdoutErrCh := asyncCopy(os.Stdout, reader)
|
||||||
return err
|
stderrErrCh := asyncCopy(os.Stderr, errReader)
|
||||||
|
|
||||||
|
return errors.Join(<-stdoutErrCh, <-stderrErrCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// asyncCopy copies all data from from dst to src asynchronously and returns a channel for reading an error value.
|
||||||
|
// This is basically an asynchronous wrapper around `io.Copy`. The returned channel is unbuffered and always is sent
|
||||||
|
// a value (either nil or the error from `io.Copy`) as soon as `io.Copy` returns.
|
||||||
|
// This function lets you copy from multiple sources into multiple destinations in parallel.
|
||||||
|
func asyncCopy(dst io.Writer, src io.Reader) <-chan error {
|
||||||
|
errCh := make(chan error)
|
||||||
|
go func(errCh chan error) {
|
||||||
|
_, err := io.Copy(dst, src)
|
||||||
|
errCh <- err
|
||||||
|
}(errCh)
|
||||||
|
|
||||||
|
return errCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func podLogs(ctx context.Context, requests []rest.ResponseWrapper) error {
|
func podLogs(ctx context.Context, requests []rest.ResponseWrapper) error {
|
||||||
|
var retErr error
|
||||||
for _, req := range requests {
|
for _, req := range requests {
|
||||||
if err := logRequest(ctx, req, os.Stdout); err != nil {
|
if err := logRequest(ctx, req, os.Stdout); err != nil {
|
||||||
return err
|
fmt.Fprintf(os.Stderr, "failed getting logs: %s\n", err)
|
||||||
|
retErr = fmt.Errorf("failed to collect logs from all Flux pods")
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func createLabelStringFromMap(m map[string]string) string {
|
func createLabelStringFromMap(m map[string]string) string {
|
||||||
|
|||||||
88
cmd/flux/logs_e2e_test.go
Normal file
88
cmd/flux/logs_e2e_test.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
//go:build e2e
|
||||||
|
// +build e2e
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLogsNoArgs(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: "logs",
|
||||||
|
assert: assertSuccess(),
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogsWrongNamespace(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: "logs --flux-namespace=default",
|
||||||
|
assert: assertError(`no Flux pods found in namespace "default"`),
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogsAllNamespaces(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: "logs --all-namespaces",
|
||||||
|
assert: assertSuccess(),
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogsSince(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: "logs --since=2m",
|
||||||
|
assert: assertSuccess(),
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogsSinceInvalid(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: "logs --since=XXX",
|
||||||
|
assert: assertError(`invalid argument "XXX" for "--since" flag: time: invalid duration "XXX"`),
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogsSinceTime(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: "logs --since-time=2021-08-06T14:26:25.546Z",
|
||||||
|
assert: assertSuccess(),
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogsSinceTimeInvalid(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: "logs --since-time=XXX",
|
||||||
|
assert: assertError("XXX is not a valid (RFC3339) time"),
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogsSinceOnlyOneAllowed(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: "logs --since=2m --since-time=2021-08-06T14:26:25.546Z",
|
||||||
|
assert: assertError("at most one of `sinceTime` or `sinceDuration` may be specified"),
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
}
|
||||||
@@ -30,73 +30,17 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLogsNoArgs(t *testing.T) {
|
|
||||||
cmd := cmdTestCase{
|
|
||||||
args: "logs",
|
|
||||||
assert: assertSuccess(),
|
|
||||||
}
|
|
||||||
cmd.runTestCmd(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogsAllNamespaces(t *testing.T) {
|
|
||||||
cmd := cmdTestCase{
|
|
||||||
args: "logs --all-namespaces",
|
|
||||||
assert: assertSuccess(),
|
|
||||||
}
|
|
||||||
cmd.runTestCmd(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogsSince(t *testing.T) {
|
|
||||||
cmd := cmdTestCase{
|
|
||||||
args: "logs --since=2m",
|
|
||||||
assert: assertSuccess(),
|
|
||||||
}
|
|
||||||
cmd.runTestCmd(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogsSinceInvalid(t *testing.T) {
|
|
||||||
cmd := cmdTestCase{
|
|
||||||
args: "logs --since=XXX",
|
|
||||||
assert: assertError(`invalid argument "XXX" for "--since" flag: time: invalid duration "XXX"`),
|
|
||||||
}
|
|
||||||
cmd.runTestCmd(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogsSinceTime(t *testing.T) {
|
|
||||||
cmd := cmdTestCase{
|
|
||||||
args: "logs --since-time=2021-08-06T14:26:25.546Z",
|
|
||||||
assert: assertSuccess(),
|
|
||||||
}
|
|
||||||
cmd.runTestCmd(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogsSinceTimeInvalid(t *testing.T) {
|
|
||||||
cmd := cmdTestCase{
|
|
||||||
args: "logs --since-time=XXX",
|
|
||||||
assert: assertError("XXX is not a valid (RFC3339) time"),
|
|
||||||
}
|
|
||||||
cmd.runTestCmd(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogsSinceOnlyOneAllowed(t *testing.T) {
|
|
||||||
cmd := cmdTestCase{
|
|
||||||
args: "logs --since=2m --since-time=2021-08-06T14:26:25.546Z",
|
|
||||||
assert: assertError("at most one of `sinceTime` or `sinceSeconds` may be specified"),
|
|
||||||
}
|
|
||||||
cmd.runTestCmd(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogRequest(t *testing.T) {
|
func TestLogRequest(t *testing.T) {
|
||||||
mapper := &testResponseMapper{}
|
mapper := &testResponseMapper{}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
namespace string
|
namespace string
|
||||||
flags *logsFlags
|
flags logsFlags
|
||||||
assertFile string
|
assertFile string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "all logs",
|
name: "all logs",
|
||||||
flags: &logsFlags{
|
flags: logsFlags{
|
||||||
tail: -1,
|
tail: -1,
|
||||||
allNamespaces: true,
|
allNamespaces: true,
|
||||||
},
|
},
|
||||||
@@ -105,14 +49,14 @@ func TestLogRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "filter by namespace",
|
name: "filter by namespace",
|
||||||
namespace: "default",
|
namespace: "default",
|
||||||
flags: &logsFlags{
|
flags: logsFlags{
|
||||||
tail: -1,
|
tail: -1,
|
||||||
},
|
},
|
||||||
assertFile: "testdata/logs/namespace.txt",
|
assertFile: "testdata/logs/namespace.txt",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "filter by kind and namespace",
|
name: "filter by kind and namespace",
|
||||||
flags: &logsFlags{
|
flags: logsFlags{
|
||||||
tail: -1,
|
tail: -1,
|
||||||
kind: "Kustomization",
|
kind: "Kustomization",
|
||||||
},
|
},
|
||||||
@@ -120,7 +64,7 @@ func TestLogRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "filter by loglevel",
|
name: "filter by loglevel",
|
||||||
flags: &logsFlags{
|
flags: logsFlags{
|
||||||
tail: -1,
|
tail: -1,
|
||||||
logLevel: "error",
|
logLevel: "error",
|
||||||
allNamespaces: true,
|
allNamespaces: true,
|
||||||
@@ -130,7 +74,7 @@ func TestLogRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "filter by namespace, name, loglevel and kind",
|
name: "filter by namespace, name, loglevel and kind",
|
||||||
namespace: "flux-system",
|
namespace: "flux-system",
|
||||||
flags: &logsFlags{
|
flags: logsFlags{
|
||||||
tail: -1,
|
tail: -1,
|
||||||
logLevel: "error",
|
logLevel: "error",
|
||||||
kind: "Kustomization",
|
kind: "Kustomization",
|
||||||
@@ -163,7 +107,7 @@ func TestLogRequest(t *testing.T) {
|
|||||||
|
|
||||||
// reset flags to default
|
// reset flags to default
|
||||||
*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace
|
*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace
|
||||||
logsArgs = &logsFlags{
|
logsArgs = logsFlags{
|
||||||
tail: -1,
|
tail: -1,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -151,6 +151,11 @@ func init() {
|
|||||||
apiServer := ""
|
apiServer := ""
|
||||||
kubeconfigArgs.APIServer = &apiServer
|
kubeconfigArgs.APIServer = &apiServer
|
||||||
rootCmd.PersistentFlags().StringVar(kubeconfigArgs.APIServer, "server", *kubeconfigArgs.APIServer, "The address and port of the Kubernetes API server")
|
rootCmd.PersistentFlags().StringVar(kubeconfigArgs.APIServer, "server", *kubeconfigArgs.APIServer, "The address and port of the Kubernetes API server")
|
||||||
|
// Update the description for kubeconfig TLS flags so that user's don't mistake it for a Flux specific flag
|
||||||
|
rootCmd.Flag("insecure-skip-tls-verify").Usage = "If true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure"
|
||||||
|
rootCmd.Flag("client-certificate").Usage = "Path to a client certificate file for TLS authentication to the Kubernetes API server"
|
||||||
|
rootCmd.Flag("certificate-authority").Usage = "Path to a cert file for the certificate authority to authenticate the Kubernetes API server"
|
||||||
|
rootCmd.Flag("client-key").Usage = "Path to a client key file for TLS authentication to the Kubernetes API server"
|
||||||
|
|
||||||
kubeclientOptions.BindFlags(rootCmd.PersistentFlags())
|
kubeclientOptions.BindFlags(rootCmd.PersistentFlags())
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func TestMain(m *testing.M) {
|
|||||||
// Install Flux.
|
// Install Flux.
|
||||||
output, err := executeCommand("install --components-extra=image-reflector-controller,image-automation-controller")
|
output, err := executeCommand("install --components-extra=image-reflector-controller,image-automation-controller")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("install falied: %s error:'%w'", output, err))
|
panic(fmt.Errorf("install failed: %s error:'%w'", output, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run tests
|
// Run tests
|
||||||
@@ -50,7 +50,7 @@ func TestMain(m *testing.M) {
|
|||||||
// Uninstall Flux
|
// Uninstall Flux
|
||||||
output, err = executeCommand("uninstall -s --keep-namespace")
|
output, err = executeCommand("uninstall -s --keep-namespace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("uninstall falied: %s error:'%w'", output, err))
|
panic(fmt.Errorf("uninstall failed: %s error:'%w'", output, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete namespace and wait for finalisation
|
// Delete namespace and wait for finalisation
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
tmpFilename := filepath.Join("/tmp", "kubeconfig-"+time.Nanosecond.String())
|
tmpFilename := filepath.Join("/tmp", "kubeconfig-"+time.Nanosecond.String())
|
||||||
os.WriteFile(tmpFilename, kubeConfig, 0644)
|
os.WriteFile(tmpFilename, kubeConfig, 0o600)
|
||||||
k8sClient, err := client.NewWithWatch(cfg, client.Options{
|
k8sClient, err := client.NewWithWatch(cfg, client.Options{
|
||||||
Scheme: utils.NewScheme(),
|
Scheme: utils.NewScheme(),
|
||||||
})
|
})
|
||||||
@@ -203,6 +203,9 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager
|
|||||||
|
|
||||||
useExistingCluster := true
|
useExistingCluster := true
|
||||||
config, err := clientcmd.BuildConfigFromFlags("", testKubeConfig)
|
config, err := clientcmd.BuildConfigFromFlags("", testKubeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
testEnv := &envtest.Environment{
|
testEnv := &envtest.Environment{
|
||||||
UseExistingCluster: &useExistingCluster,
|
UseExistingCluster: &useExistingCluster,
|
||||||
Config: config,
|
Config: config,
|
||||||
@@ -310,7 +313,7 @@ func assertGoldenTemplateFile(goldenFile string, templateValues map[string]strin
|
|||||||
if len(templateValues) > 0 {
|
if len(templateValues) > 0 {
|
||||||
fmt.Println("NOTE: -update flag passed but golden template files can't be updated, please update it manually")
|
fmt.Println("NOTE: -update flag passed but golden template files can't be updated, please update it manually")
|
||||||
} else {
|
} else {
|
||||||
if err := os.WriteFile(goldenFile, []byte(output), 0644); err != nil {
|
if err := os.WriteFile(goldenFile, []byte(output), 0o600); err != nil {
|
||||||
return fmt.Errorf("failed to update golden file '%s': %v", goldenFile, err)
|
return fmt.Errorf("failed to update golden file '%s': %v", goldenFile, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -337,8 +340,6 @@ type cmdTestCase struct {
|
|||||||
// Tests use assertFunc to assert on an output, success or failure. This
|
// Tests use assertFunc to assert on an output, success or failure. This
|
||||||
// can be a function defined by the test or existing function above.
|
// can be a function defined by the test or existing function above.
|
||||||
assert assertFunc
|
assert assertFunc
|
||||||
// Filename that contains yaml objects to load into Kubernetes
|
|
||||||
objectFile string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
|
func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
|
||||||
@@ -365,6 +366,12 @@ func executeTemplate(content string, templateValues map[string]string) (string,
|
|||||||
// Run the command and return the captured output.
|
// Run the command and return the captured output.
|
||||||
func executeCommand(cmd string) (string, error) {
|
func executeCommand(cmd string) (string, error) {
|
||||||
defer resetCmdArgs()
|
defer resetCmdArgs()
|
||||||
|
defer func() {
|
||||||
|
// need to set this explicitly because apparently its value isn't changed
|
||||||
|
// in subsequent executions which causes tests to fail that rely on the value
|
||||||
|
// of "Changed".
|
||||||
|
resumeCmd.PersistentFlags().Lookup("wait").Changed = false
|
||||||
|
}()
|
||||||
args, err := shellwords.Parse(cmd)
|
args, err := shellwords.Parse(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -392,6 +399,10 @@ func resetCmdArgs() {
|
|||||||
alertProviderArgs = alertProviderFlags{}
|
alertProviderArgs = alertProviderFlags{}
|
||||||
bootstrapArgs = NewBootstrapFlags()
|
bootstrapArgs = NewBootstrapFlags()
|
||||||
bServerArgs = bServerFlags{}
|
bServerArgs = bServerFlags{}
|
||||||
|
logsArgs = logsFlags{
|
||||||
|
tail: -1,
|
||||||
|
fluxNamespace: rootArgs.defaults.Namespace,
|
||||||
|
}
|
||||||
buildKsArgs = buildKsFlags{}
|
buildKsArgs = buildKsFlags{}
|
||||||
checkArgs = checkFlags{}
|
checkArgs = checkFlags{}
|
||||||
createArgs = createFlags{}
|
createArgs = createFlags{}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ type copyable interface {
|
|||||||
deepCopyClientObject() client.Object
|
deepCopyClientObject() client.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
// listAdapater is the analogue to adapter, but for lists; the
|
// listAdapter is the analogue to adapter, but for lists; the
|
||||||
// controller runtime distinguishes between methods dealing with
|
// controller runtime distinguishes between methods dealing with
|
||||||
// objects and lists.
|
// objects and lists.
|
||||||
type listAdapter interface {
|
type listAdapter interface {
|
||||||
|
|||||||
@@ -111,8 +111,12 @@ func pullArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Successf("source %s", meta.Source)
|
if meta.Source != "" {
|
||||||
logger.Successf("revision %s", meta.Revision)
|
logger.Successf("source %s", meta.Source)
|
||||||
|
}
|
||||||
|
if meta.Revision != "" {
|
||||||
|
logger.Successf("revision %s", meta.Revision)
|
||||||
|
}
|
||||||
logger.Successf("digest %s", meta.Digest)
|
logger.Successf("digest %s", meta.Digest)
|
||||||
logger.Successf("artifact content extracted to %s", pullArtifactArgs.output)
|
logger.Successf("artifact content extracted to %s", pullArtifactArgs.output)
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
"github.com/google/go-containerregistry/pkg/crane"
|
"github.com/google/go-containerregistry/pkg/crane"
|
||||||
"github.com/google/go-containerregistry/pkg/logs"
|
"github.com/google/go-containerregistry/pkg/logs"
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
reg "github.com/google/go-containerregistry/pkg/name"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -36,7 +35,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fluxcd/pkg/oci"
|
"github.com/fluxcd/pkg/oci"
|
||||||
"github.com/fluxcd/pkg/oci/auth/login"
|
"github.com/fluxcd/pkg/oci/auth/login"
|
||||||
client "github.com/fluxcd/pkg/oci/client"
|
"github.com/fluxcd/pkg/oci/client"
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/v2/internal/flags"
|
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||||
@@ -89,7 +88,7 @@ The command can read the credentials from '~/.docker/config.json' but they can a
|
|||||||
# Login directly to the registry provider
|
# Login directly to the registry provider
|
||||||
# You might need to export the following variable if you use local config files for AWS:
|
# You might need to export the following variable if you use local config files for AWS:
|
||||||
# export AWS_SDK_LOAD_CONFIG=1
|
# export AWS_SDK_LOAD_CONFIG=1
|
||||||
flux push artifact oci://<account>.dkr.ecr.<region>.amazonaws.com/foo:v1:$(git tag --points-at HEAD) \
|
flux push artifact oci://<account>.dkr.ecr.<region>.amazonaws.com/app-config:$(git tag --points-at HEAD) \
|
||||||
--path="./path/to/local/manifests" \
|
--path="./path/to/local/manifests" \
|
||||||
--source="$(git config --get remote.origin.url)" \
|
--source="$(git config --get remote.origin.url)" \
|
||||||
--revision="$(git tag --points-at HEAD)@sha1:$(git rev-parse HEAD)" \
|
--revision="$(git tag --points-at HEAD)@sha1:$(git rev-parse HEAD)" \
|
||||||
@@ -261,17 +260,20 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ociClient := client.NewClient(opts)
|
ociClient := client.NewClient(opts)
|
||||||
digestURL, err := ociClient.Push(ctx, url, path, meta, pushArtifactArgs.ignorePaths)
|
digestURL, err := ociClient.Push(ctx, url, path,
|
||||||
|
client.WithPushMetadata(meta),
|
||||||
|
client.WithPushIgnorePaths(pushArtifactArgs.ignorePaths...),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("pushing artifact failed: %w", err)
|
return fmt.Errorf("pushing artifact failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
digest, err := reg.NewDigest(digestURL)
|
digest, err := name.NewDigest(digestURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("artifact digest parsing failed: %w", err)
|
return fmt.Errorf("artifact digest parsing failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tag, err := reg.NewTag(url)
|
tag, err := name.NewTag(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("artifact tag parsing failed: %w", err)
|
return fmt.Errorf("artifact tag parsing failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Successf("%s annotated", reconcile.kind)
|
logger.Successf("%s annotated", reconcile.kind)
|
||||||
|
|
||||||
if reconcile.kind == notificationv1b2.AlertKind || reconcile.kind == notificationv1.ReceiverKind {
|
if reconcile.kind == notificationv1b2.AlertKind || reconcile.kind == notificationv1.ReceiverKind {
|
||||||
if err = wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err = wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isReconcileReady(ctx, kubeClient, namespacedName, reconcile.object)); err != nil {
|
isReconcileReady(kubeClient, namespacedName, reconcile.object)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,8 +124,8 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()
|
lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()
|
||||||
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
|
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
|
reconciliationHandled(kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
readyCond := apimeta.FindStatusCondition(reconcilableConditions(reconcile.object), meta.ReadyCondition)
|
readyCond := apimeta.FindStatusCondition(reconcilableConditions(reconcile.object), meta.ReadyCondition)
|
||||||
@@ -140,9 +140,8 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func reconciliationHandled(ctx context.Context, kubeClient client.Client,
|
func reconciliationHandled(kubeClient client.Client, namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionWithContextFunc {
|
||||||
namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionFunc {
|
return func(ctx context.Context) (bool, error) {
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, obj.asClientObject())
|
err := kubeClient.Get(ctx, namespacedName, obj.asClientObject())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -176,9 +175,8 @@ func requestReconciliation(ctx context.Context, kubeClient client.Client,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func isReconcileReady(ctx context.Context, kubeClient client.Client,
|
func isReconcileReady(kubeClient client.Client, namespacedName types.NamespacedName, obj reconcilable) wait.ConditionWithContextFunc {
|
||||||
namespacedName types.NamespacedName, obj reconcilable) wait.ConditionFunc {
|
return func(ctx context.Context) (bool, error) {
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, obj.asClientObject())
|
err := kubeClient.Get(ctx, namespacedName, obj.asClientObject())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Successf("Provider annotated")
|
logger.Successf("Provider annotated")
|
||||||
|
|
||||||
logger.Waitingf("waiting for reconciliation")
|
logger.Waitingf("waiting for reconciliation")
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isAlertProviderReady(ctx, kubeClient, namespacedName, &alertProvider)); err != nil {
|
isAlertProviderReady(kubeClient, namespacedName, &alertProvider)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Successf("Provider reconciliation completed")
|
logger.Successf("Provider reconciliation completed")
|
||||||
|
|||||||
@@ -43,10 +43,6 @@ func init() {
|
|||||||
reconcileImageCmd.AddCommand(reconcileImageUpdateCmd)
|
reconcileImageCmd.AddCommand(reconcileImageUpdateCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj imageUpdateAutomationAdapter) suspended() bool {
|
|
||||||
return obj.ImageUpdateAutomation.Spec.Suspend
|
|
||||||
}
|
|
||||||
|
|
||||||
func (obj imageUpdateAutomationAdapter) lastHandledReconcileRequest() string {
|
func (obj imageUpdateAutomationAdapter) lastHandledReconcileRequest() string {
|
||||||
return obj.Status.GetLastHandledReconcileRequest()
|
return obj.Status.GetLastHandledReconcileRequest()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Successf("Receiver annotated")
|
logger.Successf("Receiver annotated")
|
||||||
|
|
||||||
logger.Waitingf("waiting for Receiver reconciliation")
|
logger.Waitingf("waiting for Receiver reconciliation")
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
|
isReceiverReady(kubeClient, namespacedName, &receiver)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
|
||||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||||
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
|
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
@@ -34,7 +33,7 @@ var reconcileSourceHelmChartCmd = &cobra.Command{
|
|||||||
|
|
||||||
# Trigger a reconciliation of the HelmCharts's source and apply changes
|
# Trigger a reconciliation of the HelmCharts's source and apply changes
|
||||||
flux reconcile helmchart podinfo --with-source`,
|
flux reconcile helmchart podinfo --with-source`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1b2.GroupVersion.WithKind(sourcev1b2.HelmChartKind)),
|
||||||
RunE: reconcileWithSourceCommand{
|
RunE: reconcileWithSourceCommand{
|
||||||
apiType: helmChartType,
|
apiType: helmChartType,
|
||||||
object: helmChartAdapter{&sourcev1b2.HelmChart{}},
|
object: helmChartAdapter{&sourcev1b2.HelmChart{}},
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ func (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []strin
|
|||||||
logger.Successf("%s annotated", reconcile.kind)
|
logger.Successf("%s annotated", reconcile.kind)
|
||||||
|
|
||||||
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
|
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
|
reconciliationHandled(kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
@@ -59,8 +61,10 @@ type resumable interface {
|
|||||||
|
|
||||||
type resumeCommand struct {
|
type resumeCommand struct {
|
||||||
apiType
|
apiType
|
||||||
object resumable
|
client client.WithWatch
|
||||||
list listResumable
|
list listResumable
|
||||||
|
namespace string
|
||||||
|
shouldReconcile bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type listResumable interface {
|
type listResumable interface {
|
||||||
@@ -68,6 +72,11 @@ type listResumable interface {
|
|||||||
resumeItem(i int) resumable
|
resumeItem(i int) resumable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type reconcileResponse struct {
|
||||||
|
resumable
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
func (resume resumeCommand) run(cmd *cobra.Command, args []string) error {
|
func (resume resumeCommand) run(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 && !resumeArgs.all {
|
if len(args) < 1 && !resumeArgs.all {
|
||||||
return fmt.Errorf("%s name is required", resume.humanKind)
|
return fmt.Errorf("%s name is required", resume.humanKind)
|
||||||
@@ -80,52 +89,162 @@ func (resume resumeCommand) run(cmd *cobra.Command, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
resume.client = kubeClient
|
||||||
|
resume.namespace = *kubeconfigArgs.Namespace
|
||||||
|
|
||||||
var listOpts []client.ListOption
|
// require waiting for the object(s) if the user has not provided the --wait flag and gave exactly
|
||||||
listOpts = append(listOpts, client.InNamespace(*kubeconfigArgs.Namespace))
|
// one object to resume. This is necessary to maintain backwards compatibility with prior versions
|
||||||
if len(args) > 0 {
|
// of this command. Otherwise just follow the value of the --wait flag (including its default).
|
||||||
listOpts = append(listOpts, client.MatchingFields{
|
resume.shouldReconcile = !resumeCmd.PersistentFlags().Changed("wait") && len(args) == 1 || resumeArgs.wait
|
||||||
"metadata.name": args[0],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
err = kubeClient.List(ctx, resume.list.asClientList(), listOpts...)
|
resumables, err := resume.getPatchedResumables(ctx, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resume.list.len() == 0 {
|
var wg sync.WaitGroup
|
||||||
logger.Failuref("no %s objects found in %s namespace", resume.kind, *kubeconfigArgs.Namespace)
|
wg.Add(len(resumables))
|
||||||
return nil
|
|
||||||
|
resultChan := make(chan reconcileResponse, len(resumables))
|
||||||
|
for _, r := range resumables {
|
||||||
|
go func(res resumable) {
|
||||||
|
defer wg.Done()
|
||||||
|
resultChan <- resume.reconcile(ctx, res)
|
||||||
|
}(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < resume.list.len(); i++ {
|
go func() {
|
||||||
logger.Actionf("resuming %s %s in %s namespace", resume.humanKind, resume.list.resumeItem(i).asClientObject().GetName(), *kubeconfigArgs.Namespace)
|
defer close(resultChan)
|
||||||
obj := resume.list.resumeItem(i)
|
wg.Wait()
|
||||||
patch := client.MergeFrom(obj.deepCopyClientObject())
|
}()
|
||||||
obj.setUnsuspended()
|
|
||||||
if err := kubeClient.Patch(ctx, obj.asClientObject(), patch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Successf("%s resumed", resume.humanKind)
|
reconcileResps := make([]reconcileResponse, 0, len(resumables))
|
||||||
|
for c := range resultChan {
|
||||||
if resumeArgs.wait || !resumeArgs.all {
|
reconcileResps = append(reconcileResps, c)
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Name: resume.list.resumeItem(i).asClientObject().GetName(),
|
|
||||||
Namespace: *kubeconfigArgs.Namespace,
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Waitingf("waiting for %s reconciliation", resume.kind)
|
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
|
||||||
isReady(ctx, kubeClient, namespacedName, resume.list.resumeItem(i))); err != nil {
|
|
||||||
logger.Failuref(err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
logger.Successf("%s reconciliation completed", resume.kind)
|
|
||||||
logger.Successf(resume.list.resumeItem(i).successMessage())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resume.printMessage(reconcileResps)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getPatchedResumables returns a list of the given resumable objects that have been patched to be resumed.
|
||||||
|
// If the args slice is empty, it patches all resumable objects in the given namespace.
|
||||||
|
func (resume *resumeCommand) getPatchedResumables(ctx context.Context, args []string) ([]resumable, error) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
objs, err := resume.patch(ctx, []client.ListOption{
|
||||||
|
client.InNamespace(resume.namespace),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed patching objects: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return objs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var resumables []resumable
|
||||||
|
processed := make(map[string]struct{}, len(args))
|
||||||
|
for _, arg := range args {
|
||||||
|
if _, has := processed[arg]; has {
|
||||||
|
continue // skip object that user might have provided more than once
|
||||||
|
}
|
||||||
|
processed[arg] = struct{}{}
|
||||||
|
|
||||||
|
objs, err := resume.patch(ctx, []client.ListOption{
|
||||||
|
client.InNamespace(resume.namespace),
|
||||||
|
client.MatchingFields{
|
||||||
|
"metadata.name": arg,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resumables = append(resumables, objs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resumables, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patches resumable objects by setting their status to unsuspended.
|
||||||
|
// Returns a slice of resumables that have been patched and any error encountered during patching.
|
||||||
|
func (resume resumeCommand) patch(ctx context.Context, listOpts []client.ListOption) ([]resumable, error) {
|
||||||
|
if err := resume.client.List(ctx, resume.list.asClientList(), listOpts...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resume.list.len() == 0 {
|
||||||
|
logger.Failuref("no %s objects found in %s namespace", resume.kind, resume.namespace)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var resumables []resumable
|
||||||
|
|
||||||
|
for i := 0; i < resume.list.len(); i++ {
|
||||||
|
obj := resume.list.resumeItem(i)
|
||||||
|
logger.Actionf("resuming %s %s in %s namespace", resume.humanKind, obj.asClientObject().GetName(), resume.namespace)
|
||||||
|
|
||||||
|
patch := client.MergeFrom(obj.deepCopyClientObject())
|
||||||
|
obj.setUnsuspended()
|
||||||
|
if err := resume.client.Patch(ctx, obj.asClientObject(), patch); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resumables = append(resumables, obj)
|
||||||
|
|
||||||
|
logger.Successf("%s resumed", resume.humanKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resumables, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Waits for resumable object to be reconciled and returns the object and any error encountered while waiting.
|
||||||
|
// Returns an empty reconcileResponse, if shouldReconcile is false.
|
||||||
|
func (resume resumeCommand) reconcile(ctx context.Context, res resumable) reconcileResponse {
|
||||||
|
if !resume.shouldReconcile {
|
||||||
|
return reconcileResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespacedName := types.NamespacedName{
|
||||||
|
Name: res.asClientObject().GetName(),
|
||||||
|
Namespace: resume.namespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Waitingf("waiting for %s reconciliation", resume.kind)
|
||||||
|
|
||||||
|
if err := wait.PollUntilContextTimeout(ctx, rootArgs.pollInterval, rootArgs.timeout, true,
|
||||||
|
isReady(resume.client, namespacedName, res)); err != nil {
|
||||||
|
return reconcileResponse{
|
||||||
|
resumable: res,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reconcileResponse{
|
||||||
|
resumable: res,
|
||||||
|
err: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorts the given reconcileResponses by resumable name and prints the success/error message for each response.
|
||||||
|
func (resume resumeCommand) printMessage(responses []reconcileResponse) {
|
||||||
|
sort.Slice(responses, func(i, j int) bool {
|
||||||
|
r1, r2 := responses[i], responses[j]
|
||||||
|
if r1.resumable == nil || r2.resumable == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return r1.asClientObject().GetName() <= r2.asClientObject().GetName()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Print success/error message.
|
||||||
|
for _, r := range responses {
|
||||||
|
if r.resumable == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r.err != nil {
|
||||||
|
logger.Failuref(r.err.Error())
|
||||||
|
}
|
||||||
|
logger.Successf("%s %s reconciliation completed", resume.kind, r.asClientObject().GetName())
|
||||||
|
logger.Successf(r.successMessage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ var resumeAlertCmd = &cobra.Command{
|
|||||||
Long: `The resume command marks a previously suspended Alert resource for reconciliation and waits for it to
|
Long: `The resume command marks a previously suspended Alert resource for reconciliation and waits for it to
|
||||||
finish the apply.`,
|
finish the apply.`,
|
||||||
Example: ` # Resume reconciliation for an existing Alert
|
Example: ` # Resume reconciliation for an existing Alert
|
||||||
flux resume alert main`,
|
flux resume alert main
|
||||||
|
|
||||||
|
# Resume reconciliation for multiple Alerts
|
||||||
|
flux resume alert main-1 main-2`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: alertType,
|
apiType: alertType,
|
||||||
object: alertAdapter{¬ificationv1.Alert{}},
|
|
||||||
list: &alertListAdapter{¬ificationv1.AlertList{}},
|
list: &alertListAdapter{¬ificationv1.AlertList{}},
|
||||||
}.run,
|
}.run,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,13 @@ var resumeHrCmd = &cobra.Command{
|
|||||||
Long: `The resume command marks a previously suspended HelmRelease resource for reconciliation and waits for it to
|
Long: `The resume command marks a previously suspended HelmRelease resource for reconciliation and waits for it to
|
||||||
finish the apply.`,
|
finish the apply.`,
|
||||||
Example: ` # Resume reconciliation for an existing Helm release
|
Example: ` # Resume reconciliation for an existing Helm release
|
||||||
flux resume hr podinfo`,
|
flux resume hr podinfo
|
||||||
|
|
||||||
|
# Resume reconciliation for multiple Helm releases
|
||||||
|
flux resume hr podinfo-1 podinfo-2`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: helmReleaseType,
|
apiType: helmReleaseType,
|
||||||
object: helmReleaseAdapter{&helmv2.HelmRelease{}},
|
|
||||||
list: helmReleaseListAdapter{&helmv2.HelmReleaseList{}},
|
list: helmReleaseListAdapter{&helmv2.HelmReleaseList{}},
|
||||||
}.run,
|
}.run,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ var resumeImageRepositoryCmd = &cobra.Command{
|
|||||||
Short: "Resume a suspended ImageRepository",
|
Short: "Resume a suspended ImageRepository",
|
||||||
Long: `The resume command marks a previously suspended ImageRepository resource for reconciliation and waits for it to finish.`,
|
Long: `The resume command marks a previously suspended ImageRepository resource for reconciliation and waits for it to finish.`,
|
||||||
Example: ` # Resume reconciliation for an existing ImageRepository
|
Example: ` # Resume reconciliation for an existing ImageRepository
|
||||||
flux resume image repository alpine`,
|
flux resume image repository alpine
|
||||||
|
|
||||||
|
# Resume reconciliation for multiple ImageRepositories
|
||||||
|
flux resume image repository alpine-1 alpine-2`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: imageRepositoryType,
|
apiType: imageRepositoryType,
|
||||||
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
|
||||||
list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
|
list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
|
||||||
}.run,
|
}.run,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ var resumeImageUpdateCmd = &cobra.Command{
|
|||||||
Short: "Resume a suspended ImageUpdateAutomation",
|
Short: "Resume a suspended ImageUpdateAutomation",
|
||||||
Long: `The resume command marks a previously suspended ImageUpdateAutomation resource for reconciliation and waits for it to finish.`,
|
Long: `The resume command marks a previously suspended ImageUpdateAutomation resource for reconciliation and waits for it to finish.`,
|
||||||
Example: ` # Resume reconciliation for an existing ImageUpdateAutomation
|
Example: ` # Resume reconciliation for an existing ImageUpdateAutomation
|
||||||
flux resume image update latest-images`,
|
flux resume image update latest-images
|
||||||
|
|
||||||
|
# Resume reconciliation for multiple ImageUpdateAutomations
|
||||||
|
flux resume image update latest-images-1 latest-images-2`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: imageUpdateAutomationType,
|
apiType: imageUpdateAutomationType,
|
||||||
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
|
||||||
list: imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
|
list: imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
|
||||||
}.run,
|
}.run,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,13 @@ var resumeKsCmd = &cobra.Command{
|
|||||||
Long: `The resume command marks a previously suspended Kustomization resource for reconciliation and waits for it to
|
Long: `The resume command marks a previously suspended Kustomization resource for reconciliation and waits for it to
|
||||||
finish the apply.`,
|
finish the apply.`,
|
||||||
Example: ` # Resume reconciliation for an existing Kustomization
|
Example: ` # Resume reconciliation for an existing Kustomization
|
||||||
flux resume ks podinfo`,
|
flux resume ks podinfo
|
||||||
|
|
||||||
|
# Resume reconciliation for multiple Kustomizations
|
||||||
|
flux resume ks podinfo-1 podinfo-2`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: kustomizationType,
|
apiType: kustomizationType,
|
||||||
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
|
||||||
list: kustomizationListAdapter{&kustomizev1.KustomizationList{}},
|
list: kustomizationListAdapter{&kustomizev1.KustomizationList{}},
|
||||||
}.run,
|
}.run,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ var resumeReceiverCmd = &cobra.Command{
|
|||||||
Long: `The resume command marks a previously suspended Receiver resource for reconciliation and waits for it to
|
Long: `The resume command marks a previously suspended Receiver resource for reconciliation and waits for it to
|
||||||
finish the apply.`,
|
finish the apply.`,
|
||||||
Example: ` # Resume reconciliation for an existing Receiver
|
Example: ` # Resume reconciliation for an existing Receiver
|
||||||
flux resume receiver main`,
|
flux resume receiver main
|
||||||
|
|
||||||
|
# Resume reconciliation for multiple Receivers
|
||||||
|
flux resume receiver main-1 main-2`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: receiverType,
|
apiType: receiverType,
|
||||||
object: receiverAdapter{¬ificationv1.Receiver{}},
|
|
||||||
list: receiverListAdapter{¬ificationv1.ReceiverList{}},
|
list: receiverListAdapter{¬ificationv1.ReceiverList{}},
|
||||||
}.run,
|
}.run,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ var resumeSourceBucketCmd = &cobra.Command{
|
|||||||
Short: "Resume a suspended Bucket",
|
Short: "Resume a suspended Bucket",
|
||||||
Long: `The resume command marks a previously suspended Bucket resource for reconciliation and waits for it to finish.`,
|
Long: `The resume command marks a previously suspended Bucket resource for reconciliation and waits for it to finish.`,
|
||||||
Example: ` # Resume reconciliation for an existing Bucket
|
Example: ` # Resume reconciliation for an existing Bucket
|
||||||
flux resume source bucket podinfo`,
|
flux resume source bucket podinfo
|
||||||
|
|
||||||
|
# Resume reconciliation for multiple Buckets
|
||||||
|
flux resume source bucket podinfo-1 podinfo-2`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: bucketType,
|
apiType: bucketType,
|
||||||
object: bucketAdapter{&sourcev1.Bucket{}},
|
|
||||||
list: bucketListAdapter{&sourcev1.BucketList{}},
|
list: bucketListAdapter{&sourcev1.BucketList{}},
|
||||||
}.run,
|
}.run,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ var resumeSourceHelmChartCmd = &cobra.Command{
|
|||||||
Short: "Resume a suspended HelmChart",
|
Short: "Resume a suspended HelmChart",
|
||||||
Long: `The resume command marks a previously suspended HelmChart resource for reconciliation and waits for it to finish.`,
|
Long: `The resume command marks a previously suspended HelmChart resource for reconciliation and waits for it to finish.`,
|
||||||
Example: ` # Resume reconciliation for an existing HelmChart
|
Example: ` # Resume reconciliation for an existing HelmChart
|
||||||
flux resume source chart podinfo`,
|
flux resume source chart podinfo
|
||||||
|
|
||||||
|
# Resume reconciliation for multiple HelmCharts
|
||||||
|
flux resume source chart podinfo-1 podinfo-2`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),
|
||||||
RunE: resumeCommand{
|
RunE: resumeCommand{
|
||||||
apiType: helmChartType,
|
apiType: helmChartType,
|
||||||
object: &helmChartAdapter{&sourcev1.HelmChart{}},
|
|
||||||
list: &helmChartListAdapter{&sourcev1.HelmChartList{}},
|
list: &helmChartListAdapter{&sourcev1.HelmChartList{}},
|
||||||
}.run,
|
}.run,
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user