Compare commits
190 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dfd0bc2e1 | ||
|
|
10ff99542f | ||
|
|
2449030ab8 | ||
|
|
3e85901f40 | ||
|
|
73b1576f81 | ||
|
|
cdb5b7c9a2 | ||
|
|
d9331b0c91 | ||
|
|
b6a8163dd9 | ||
|
|
185252ba48 | ||
|
|
043d37921b | ||
|
|
02fb8d9958 | ||
|
|
cfa6c0a178 | ||
|
|
e8b52bf2fc | ||
|
|
85fbb780bf | ||
|
|
bd9f9bf518 | ||
|
|
077860fff1 | ||
|
|
d29a4ee4d2 | ||
|
|
6d2e34e9b2 | ||
|
|
0b6969537b | ||
|
|
dc6b0d0f0d | ||
|
|
b4dbb178fe | ||
|
|
4cf5290989 | ||
|
|
6ffd2222c2 | ||
|
|
e7725911a7 | ||
|
|
0c1664cd01 | ||
|
|
0239307d8e | ||
|
|
9f10b6be1b | ||
|
|
01f613b39e | ||
|
|
b775d11a70 | ||
|
|
022576697f | ||
|
|
065d0b2c06 | ||
|
|
ed4718205a | ||
|
|
a29d0c536d | ||
|
|
eaeb8ca5c1 | ||
|
|
2092c14aca | ||
|
|
69f38b8c77 | ||
|
|
d2cdd02a57 | ||
|
|
095c8323a1 | ||
|
|
accb4c915e | ||
|
|
242809f61d | ||
|
|
c4907cf6c6 | ||
|
|
a4b9191fa3 | ||
|
|
5fd3d0bd41 | ||
|
|
ba6da23323 | ||
|
|
0328bb14ce | ||
|
|
2b7a0f3fd4 | ||
|
|
527886bea0 | ||
|
|
98078a0c65 | ||
|
|
ca660b7ba5 | ||
|
|
ed93e93b81 | ||
|
|
80419f00db | ||
|
|
01946facb3 | ||
|
|
08c1bd7653 | ||
|
|
ebf9188c6a | ||
|
|
382c6d5885 | ||
|
|
384c60a988 | ||
|
|
0078147587 | ||
|
|
d79bedf2bc | ||
|
|
14b31b321c | ||
|
|
309b9b52f8 | ||
|
|
5d063e7390 | ||
|
|
e7ba9b5624 | ||
|
|
81f6fa598f | ||
|
|
d9eabcdbf7 | ||
|
|
bb3562427b | ||
|
|
8a5bba80bf | ||
|
|
ff2833c4d1 | ||
|
|
45ba845f23 | ||
|
|
771a14fcf6 | ||
|
|
c8ff861d00 | ||
|
|
0f05ce3605 | ||
|
|
38a3f3ba11 | ||
|
|
659a19cd80 | ||
|
|
baaa466c0f | ||
|
|
168c65bb6e | ||
|
|
6003d11156 | ||
|
|
1f16b6d639 | ||
|
|
54bb4b2efd | ||
|
|
f54770c21a | ||
|
|
1244a62deb | ||
|
|
2fe55bcdde | ||
|
|
9943690855 | ||
|
|
89c46a6379 | ||
|
|
a0d4530cc0 | ||
|
|
6db84269af | ||
|
|
2cd3c32ca7 | ||
|
|
9740fecc35 | ||
|
|
433492791b | ||
|
|
7d3c63ad74 | ||
|
|
a6538b117e | ||
|
|
d54e7559a5 | ||
|
|
bb9eca7232 | ||
|
|
b5027d8f3f | ||
|
|
00a134e23f | ||
|
|
aab3452773 | ||
|
|
5903dfc627 | ||
|
|
a5272b2b39 | ||
|
|
771fc20bb2 | ||
|
|
b5f48bee78 | ||
|
|
ff78af5808 | ||
|
|
7eab649abc | ||
|
|
1b5db157b1 | ||
|
|
0fa9aebf7a | ||
|
|
9fb9965ffe | ||
|
|
02c9f29499 | ||
|
|
c6243ad002 | ||
|
|
f4650c7a84 | ||
|
|
6df691598e | ||
|
|
26964b7f88 | ||
|
|
71712ae1c3 | ||
|
|
0b6c979e47 | ||
|
|
cc2cbc2519 | ||
|
|
8043ae961a | ||
|
|
b1d1d30cdb | ||
|
|
28d606cb0e | ||
|
|
e9c5b6e917 | ||
|
|
68f96235fc | ||
|
|
12f22ddeba | ||
|
|
9db5c644a6 | ||
|
|
ef6a1fb09b | ||
|
|
99002f92f4 | ||
|
|
298d6a1a15 | ||
|
|
5a21f50230 | ||
|
|
5263dabd22 | ||
|
|
9b649f6c72 | ||
|
|
b903cd5b68 | ||
|
|
890b5c5202 | ||
|
|
a14e88d04d | ||
|
|
58e09b4c68 | ||
|
|
631201d541 | ||
|
|
0fbeb6d2cd | ||
|
|
11f8e2ffde | ||
|
|
055eb4a61a | ||
|
|
30c1c5c3d3 | ||
|
|
e034ec3207 | ||
|
|
8edc4bd24b | ||
|
|
6e1672f73c | ||
|
|
5e1f6f7317 | ||
|
|
386780ba12 | ||
|
|
e785971ba8 | ||
|
|
daaae07649 | ||
|
|
6cd567dc66 | ||
|
|
a541a7ee85 | ||
|
|
43572bba04 | ||
|
|
2a3a4456c1 | ||
|
|
15f8e6369b | ||
|
|
cfad9a19eb | ||
|
|
e4c3136433 | ||
|
|
73b8a26850 | ||
|
|
aa533b28fb | ||
|
|
9d70e09a57 | ||
|
|
17e18985e6 | ||
|
|
7c39aaf463 | ||
|
|
bae5c125e8 | ||
|
|
1c84fa0d97 | ||
|
|
6f583f9f0e | ||
|
|
217574b75c | ||
|
|
1378530aeb | ||
|
|
0b10ed4d88 | ||
|
|
a2887f5776 | ||
|
|
0f1d27f1e6 | ||
|
|
850ab0942b | ||
|
|
f5ae8f44b4 | ||
|
|
7f98cfd506 | ||
|
|
bc45a79b92 | ||
|
|
5003cf674d | ||
|
|
bc9cbc387c | ||
|
|
60a1e78869 | ||
|
|
37f5587085 | ||
|
|
fa6e3d3706 | ||
|
|
bb8bc875b4 | ||
|
|
b3dca737be | ||
|
|
9094f85487 | ||
|
|
1256bbfbaf | ||
|
|
24fe74f2f6 | ||
|
|
908f501e03 | ||
|
|
35507c7854 | ||
|
|
eb7102ecac | ||
|
|
ade6bfcbca | ||
|
|
fa98403aa8 | ||
|
|
3f0cb1637c | ||
|
|
42011d028e | ||
|
|
307bb0dea1 | ||
|
|
ec2a8347d4 | ||
|
|
e99b1c3ed8 | ||
|
|
99825f2663 | ||
|
|
afffdfbc5c | ||
|
|
cd874acfd5 | ||
|
|
34edbf469e | ||
|
|
d9ed30e436 |
19
.github/aur/flux-bin/PKGBUILD.template
vendored
19
.github/aur/flux-bin/PKGBUILD.template
vendored
@@ -8,18 +8,20 @@ pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
|
||||
url="https://fluxcd.io/"
|
||||
arch=("x86_64" "armv6h" "armv7h" "aarch64")
|
||||
license=("APACHE")
|
||||
optdepends=("kubectl")
|
||||
optdepends=('kubectl: for apply actions on the Kubernetes cluster',
|
||||
'bash-completion: auto-completion for flux in Bash',
|
||||
'zsh-completions: auto-completion for flux in ZSH')
|
||||
source_x86_64=(
|
||||
"$pkgname-$pkgver.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_amd64.tar.gz"
|
||||
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_amd64.tar.gz"
|
||||
)
|
||||
source_armv6h=(
|
||||
"$pkgname-$pkgver.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm.tar.gz"
|
||||
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm.tar.gz"
|
||||
)
|
||||
source_armv7h=(
|
||||
"$pkgname-$pkgver.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm.tar.gz"
|
||||
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm.tar.gz"
|
||||
)
|
||||
source_aarch64=(
|
||||
"$pkgname-$pkgver.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm64.tar.gz"
|
||||
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm64.tar.gz"
|
||||
)
|
||||
sha256sums_x86_64=(
|
||||
${SHA256SUM_AMD64}
|
||||
@@ -33,7 +35,12 @@ sha256sums_armv7h=(
|
||||
sha256sums_aarch64=(
|
||||
${SHA256SUM_ARM64}
|
||||
)
|
||||
_srcname=flux
|
||||
|
||||
package() {
|
||||
install -Dm755 flux "$pkgdir/usr/bin/flux"
|
||||
install -Dm755 ${_srcname} "${pkgdir}/usr/bin/${_srcname}"
|
||||
|
||||
"${pkgdir}/usr/bin/${_srcname}" completion bash | install -Dm644 /dev/stdin "${pkgdir}/usr/share/bash-completion/completions/${_srcname}"
|
||||
"${pkgdir}/usr/bin/${_srcname}" completion fish | install -Dm644 /dev/stdin "${pkgdir}/usr/share/fish/vendor_completions.d/${_srcname}.fish"
|
||||
"${pkgdir}/usr/bin/${_srcname}" completion zsh | install -Dm644 /dev/stdin "${pkgdir}/usr/share/zsh/site-functions/_${_srcname}"
|
||||
}
|
||||
|
||||
28
.github/aur/flux-go/PKGBUILD.template
vendored
28
.github/aur/flux-go/PKGBUILD.template
vendored
@@ -12,32 +12,40 @@ provides=("flux-bin")
|
||||
conflicts=("flux-bin")
|
||||
replaces=("flux-cli")
|
||||
depends=("glibc")
|
||||
makedepends=("go")
|
||||
optdepends=("kubectl")
|
||||
makedepends=('go>=1.16', 'kustomize>=3.0')
|
||||
optdepends=('kubectl: for apply actions on the Kubernetes cluster',
|
||||
'bash-completion: auto-completion for flux in Bash',
|
||||
'zsh-completions: auto-completion for flux in ZSH')
|
||||
source=(
|
||||
"$pkgname-$pkgver.tar.gz::https://github.com/fluxcd/flux2/archive/v$pkgver.tar.gz"
|
||||
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/archive/v${pkgver}.tar.gz"
|
||||
)
|
||||
sha256sums=(
|
||||
${SHA256SUM}
|
||||
)
|
||||
_srcname=flux
|
||||
|
||||
build() {
|
||||
cd "flux2-$pkgver"
|
||||
cd "flux2-${pkgver}"
|
||||
export CGO_LDFLAGS="$LDFLAGS"
|
||||
export CGO_CFLAGS="$CFLAGS"
|
||||
export CGO_CXXFLAGS="$CXXFLAGS"
|
||||
export CGO_CPPFLAGS="$CPPFLAGS"
|
||||
export GOFLAGS="-buildmode=pie -trimpath -ldflags=-linkmode=external -mod=readonly -modcacherw"
|
||||
go build -ldflags "-X main.VERSION=$pkgver" -o flux-bin ./cmd/flux
|
||||
export GOFLAGS="-buildmode=pie -trimpath -mod=readonly -modcacherw"
|
||||
./manifests/scripts/bundle.sh "${PWD}/manifests" "${PWD}/cmd/flux/manifests"
|
||||
go build -ldflags "-linkmode=external -X main.VERSION=${pkgver}" -o ${_srcname} ./cmd/flux
|
||||
}
|
||||
|
||||
check() {
|
||||
cd "flux2-$pkgver"
|
||||
cd "flux2-${pkgver}"
|
||||
make test
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "flux2-$pkgver"
|
||||
install -Dm755 flux-bin "$pkgdir/usr/bin/flux"
|
||||
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
||||
cd "flux2-${pkgver}"
|
||||
install -Dm755 ${_srcname} "${pkgdir}/usr/bin/${_srcname}"
|
||||
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
||||
|
||||
"${pkgdir}/usr/bin/${_srcname}" completion bash | install -Dm644 /dev/stdin "${pkgdir}/usr/share/bash-completion/completions/${_srcname}"
|
||||
"${pkgdir}/usr/bin/${_srcname}" completion fish | install -Dm644 /dev/stdin "${pkgdir}/usr/share/fish/vendor_completions.d/${_srcname}.fish"
|
||||
"${pkgdir}/usr/bin/${_srcname}" completion zsh | install -Dm644 /dev/stdin "${pkgdir}/usr/share/zsh/site-functions/_${_srcname}"
|
||||
}
|
||||
|
||||
20
.github/aur/flux-scm/PKGBUILD.template
vendored
20
.github/aur/flux-scm/PKGBUILD.template
vendored
@@ -11,12 +11,15 @@ license=("APACHE")
|
||||
provides=("flux-bin")
|
||||
conflicts=("flux-bin")
|
||||
depends=("glibc")
|
||||
makedepends=("go")
|
||||
optdepends=("kubectl")
|
||||
makedepends=('go>=1.16', 'kustomize>=3.0')
|
||||
optdepends=('kubectl: for apply actions on the Kubernetes cluster',
|
||||
'bash-completion: auto-completion for flux in Bash',
|
||||
'zsh-completions: auto-completion for flux in ZSH')
|
||||
source=(
|
||||
"git+https://github.com/fluxcd/flux2.git"
|
||||
)
|
||||
md5sums=('SKIP')
|
||||
_srcname=flux
|
||||
|
||||
pkgver() {
|
||||
cd "flux2"
|
||||
@@ -29,8 +32,9 @@ build() {
|
||||
export CGO_CFLAGS="$CFLAGS"
|
||||
export CGO_CXXFLAGS="$CXXFLAGS"
|
||||
export CGO_CPPFLAGS="$CPPFLAGS"
|
||||
export GOFLAGS="-buildmode=pie -trimpath -ldflags=-linkmode=external -mod=readonly -modcacherw"
|
||||
go build -ldflags "-X main.VERSION=$pkgver" -o flux-bin ./cmd/flux
|
||||
export GOFLAGS="-buildmode=pie -trimpath -mod=readonly -modcacherw"
|
||||
make cmd/flux/manifests
|
||||
go build -ldflags "-linkmode=external -X main.VERSION=${pkgver}" -o ${_srcname} ./cmd/flux
|
||||
}
|
||||
|
||||
check() {
|
||||
@@ -40,6 +44,10 @@ check() {
|
||||
|
||||
package() {
|
||||
cd "flux2"
|
||||
install -Dm755 flux-bin "$pkgdir/usr/bin/flux"
|
||||
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
||||
install -Dm755 ${_srcname} "${pkgdir}/usr/bin/${_srcname}"
|
||||
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
||||
|
||||
"${pkgdir}/usr/bin/${_srcname}" completion bash | install -Dm644 /dev/stdin "${pkgdir}/usr/share/bash-completion/completions/${_srcname}"
|
||||
"${pkgdir}/usr/bin/${_srcname}" completion fish | install -Dm644 /dev/stdin "${pkgdir}/usr/share/fish/vendor_completions.d/${_srcname}.fish"
|
||||
"${pkgdir}/usr/bin/${_srcname}" completion zsh | install -Dm644 /dev/stdin "${pkgdir}/usr/share/zsh/site-functions/_${_srcname}"
|
||||
}
|
||||
|
||||
55
.github/workflows/bootstrap.yaml
vendored
55
.github/workflows/bootstrap.yaml
vendored
@@ -2,12 +2,14 @@ name: bootstrap
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
github:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -15,58 +17,69 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
${{ runner.os }}-go1.16-
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15.x
|
||||
go-version: 1.16.x
|
||||
- name: Setup Kubernetes
|
||||
uses: engineerd/setup-kind@v0.5.0
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg//actions/kustomize@main
|
||||
- name: Build
|
||||
run: |
|
||||
make cmd/flux/manifests
|
||||
go build -o /tmp/flux ./cmd/flux
|
||||
- name: Set outputs
|
||||
id: vars
|
||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||
- name: Build
|
||||
run: sudo go build -o ./bin/flux ./cmd/flux
|
||||
run: |
|
||||
REPOSITORY_NAME=${{ github.event.repository.name }}
|
||||
BRANCH_NAME=${GITHUB_REF##*/}
|
||||
COMMIT_SHA=$(git rev-parse HEAD)
|
||||
PSEUDO_RAND_SUFFIX=$(echo "${BRANCH_NAME}-${COMMIT_SHA}" | shasum | awk '{print $1}')
|
||||
TEST_REPO_NAME="${REPOSITORY_NAME}-${PSEUDO_RAND_SUFFIX}"
|
||||
echo "::set-output name=test_repo_name::$TEST_REPO_NAME"
|
||||
- name: bootstrap init
|
||||
run: |
|
||||
./bin/flux bootstrap github --manifests ./manifests/install/ \
|
||||
/tmp/flux bootstrap github --manifests ./manifests/install/ \
|
||||
--owner=fluxcd-testing \
|
||||
--repository=flux-test-${{ steps.vars.outputs.sha_short }} \
|
||||
--repository=${{ steps.vars.outputs.test_repo_name }} \
|
||||
--branch=main \
|
||||
--path=test-cluster
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
- name: bootstrap no-op
|
||||
run: |
|
||||
./bin/flux bootstrap github --manifests ./manifests/install/ \
|
||||
/tmp/flux bootstrap github --manifests ./manifests/install/ \
|
||||
--owner=fluxcd-testing \
|
||||
--repository=flux-test-${{ steps.vars.outputs.sha_short }} \
|
||||
--repository=${{ steps.vars.outputs.test_repo_name }} \
|
||||
--branch=main \
|
||||
--path=test-cluster
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
- name: uninstall
|
||||
run: |
|
||||
./bin/flux uninstall --resources --crds -s --timeout=10m
|
||||
/tmp/flux uninstall -s --keep-namespace
|
||||
kubectl delete ns flux-system --timeout=10m --wait=true
|
||||
- name: bootstrap reinstall
|
||||
run: |
|
||||
./bin/flux bootstrap github --manifests ./manifests/install/ \
|
||||
/tmp/flux bootstrap github --manifests ./manifests/install/ \
|
||||
--owner=fluxcd-testing \
|
||||
--repository=flux-test-${{ steps.vars.outputs.sha_short }} \
|
||||
--repository=${{ steps.vars.outputs.test_repo_name }} \
|
||||
--branch=main \
|
||||
--path=test-cluster
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
- name: delete repository
|
||||
run: |
|
||||
./bin/flux bootstrap github --manifests ./manifests/install/ \
|
||||
--owner=fluxcd-testing \
|
||||
--repository=flux-test-${{ steps.vars.outputs.sha_short }} \
|
||||
--branch=main \
|
||||
--path=test-cluster \
|
||||
--delete
|
||||
curl \
|
||||
-X DELETE \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token ${GITHUB_TOKEN}" \
|
||||
--fail --silent \
|
||||
https://api.github.com/repos/fluxcd-testing/${{ steps.vars.outputs.test_repo_name }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
|
||||
- name: Debug failure
|
||||
|
||||
7
.github/workflows/docs.yaml
vendored
7
.github/workflows/docs.yaml
vendored
@@ -1,9 +1,8 @@
|
||||
name: Publish docs via GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- docs*
|
||||
- main
|
||||
branches: [ 'docs*', main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -17,7 +16,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
controller_version() {
|
||||
sed -n "s/.*$1\/archive\/\(.*\).zip.*/\1/p;n" manifests/bases/$1/kustomization.yaml
|
||||
sed -n "s/.*$1\/releases\/download\/\(.*\)\/.*/\1/p;n" manifests/bases/$1/kustomization.yaml
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
105
.github/workflows/e2e.yaml
vendored
105
.github/workflows/e2e.yaml
vendored
@@ -1,10 +1,10 @@
|
||||
name: e2e
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
kind:
|
||||
@@ -16,13 +16,13 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
${{ runner.os }}-go1.16-
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15.x
|
||||
go-version: 1.16.x
|
||||
- name: Setup Kubernetes
|
||||
uses: engineerd/setup-kind@v0.5.0
|
||||
with:
|
||||
@@ -33,6 +33,8 @@ jobs:
|
||||
run: |
|
||||
kubectl apply -f https://docs.projectcalico.org/v3.16/manifests/calico.yaml
|
||||
kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg//actions/kustomize@main
|
||||
- name: Run test
|
||||
run: make test
|
||||
- name: Check if working tree is dirty
|
||||
@@ -43,43 +45,44 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
- name: Build
|
||||
run: sudo go build -o ./bin/flux ./cmd/flux
|
||||
run: |
|
||||
go build -o /tmp/flux ./cmd/flux
|
||||
- name: flux check --pre
|
||||
run: |
|
||||
./bin/flux check --pre
|
||||
/tmp/flux check --pre
|
||||
- name: flux install --manifests
|
||||
run: |
|
||||
./bin/flux install --manifests ./manifests/install/
|
||||
/tmp/flux install --manifests ./manifests/install/
|
||||
- name: flux create secret
|
||||
run: |
|
||||
./bin/flux create secret git git-ssh-test \
|
||||
/tmp/flux create secret git git-ssh-test \
|
||||
--url ssh://git@github.com/stefanprodan/podinfo
|
||||
./bin/flux create secret git git-https-test \
|
||||
/tmp/flux create secret git git-https-test \
|
||||
--url https://github.com/stefanprodan/podinfo \
|
||||
--username=test --password=test
|
||||
./bin/flux create secret helm helm-test \
|
||||
/tmp/flux create secret helm helm-test \
|
||||
--username=test --password=test
|
||||
- name: flux create source git
|
||||
run: |
|
||||
./bin/flux create source git podinfo \
|
||||
/tmp/flux create source git podinfo \
|
||||
--url https://github.com/stefanprodan/podinfo \
|
||||
--tag-semver=">=3.2.3"
|
||||
- name: flux create source git export apply
|
||||
run: |
|
||||
./bin/flux create source git podinfo-export \
|
||||
/tmp/flux create source git podinfo-export \
|
||||
--url https://github.com/stefanprodan/podinfo \
|
||||
--tag-semver=">=3.2.3" \
|
||||
--export | kubectl apply -f -
|
||||
./bin/flux delete source git podinfo-export --silent
|
||||
/tmp/flux delete source git podinfo-export --silent
|
||||
- name: flux get sources git
|
||||
run: |
|
||||
./bin/flux get sources git
|
||||
/tmp/flux get sources git
|
||||
- name: flux get sources git --all-namespaces
|
||||
run: |
|
||||
./bin/flux get sources git --all-namespaces
|
||||
/tmp/flux get sources git --all-namespaces
|
||||
- name: flux create kustomization
|
||||
run: |
|
||||
./bin/flux create kustomization podinfo \
|
||||
/tmp/flux create kustomization podinfo \
|
||||
--source=podinfo \
|
||||
--path="./deploy/overlays/dev" \
|
||||
--prune=true \
|
||||
@@ -90,106 +93,112 @@ jobs:
|
||||
--health-check-timeout=3m
|
||||
- name: flux reconcile kustomization --with-source
|
||||
run: |
|
||||
./bin/flux reconcile kustomization podinfo --with-source
|
||||
/tmp/flux reconcile kustomization podinfo --with-source
|
||||
- name: flux get kustomizations
|
||||
run: |
|
||||
./bin/flux get kustomizations
|
||||
/tmp/flux get kustomizations
|
||||
- name: flux get kustomizations --all-namespaces
|
||||
run: |
|
||||
./bin/flux get kustomizations --all-namespaces
|
||||
/tmp/flux get kustomizations --all-namespaces
|
||||
- name: flux suspend kustomization
|
||||
run: |
|
||||
./bin/flux suspend kustomization podinfo
|
||||
/tmp/flux suspend kustomization podinfo
|
||||
- name: flux resume kustomization
|
||||
run: |
|
||||
./bin/flux resume kustomization podinfo
|
||||
/tmp/flux resume kustomization podinfo
|
||||
- name: flux export
|
||||
run: |
|
||||
./bin/flux export source git --all
|
||||
./bin/flux export kustomization --all
|
||||
/tmp/flux export source git --all
|
||||
/tmp/flux export kustomization --all
|
||||
- name: flux delete kustomization
|
||||
run: |
|
||||
./bin/flux delete kustomization podinfo --silent
|
||||
/tmp/flux delete kustomization podinfo --silent
|
||||
- name: flux create source helm
|
||||
run: |
|
||||
./bin/flux create source helm podinfo \
|
||||
/tmp/flux create source helm podinfo \
|
||||
--url https://stefanprodan.github.io/podinfo
|
||||
- name: flux create helmrelease --source=HelmRepository/podinfo
|
||||
run: |
|
||||
./bin/flux create hr podinfo-helm \
|
||||
/tmp/flux create hr podinfo-helm \
|
||||
--target-namespace=default \
|
||||
--source=HelmRepository/podinfo \
|
||||
--chart=podinfo \
|
||||
--chart-version=">4.0.0 <5.0.0"
|
||||
- name: flux create helmrelease --source=GitRepository/podinfo
|
||||
run: |
|
||||
./bin/flux create hr podinfo-git \
|
||||
/tmp/flux create hr podinfo-git \
|
||||
--target-namespace=default \
|
||||
--source=GitRepository/podinfo \
|
||||
--chart=./charts/podinfo
|
||||
- name: flux reconcile helmrelease --with-source
|
||||
run: |
|
||||
./bin/flux reconcile helmrelease podinfo-git --with-source
|
||||
/tmp/flux reconcile helmrelease podinfo-git --with-source
|
||||
- name: flux get helmreleases
|
||||
run: |
|
||||
./bin/flux get helmreleases
|
||||
/tmp/flux get helmreleases
|
||||
- name: flux get helmreleases --all-namespaces
|
||||
run: |
|
||||
./bin/flux get helmreleases --all-namespaces
|
||||
/tmp/flux get helmreleases --all-namespaces
|
||||
- name: flux export helmrelease
|
||||
run: |
|
||||
./bin/flux export hr --all
|
||||
/tmp/flux export hr --all
|
||||
- name: flux delete helmrelease podinfo-helm
|
||||
run: |
|
||||
./bin/flux delete hr podinfo-helm --silent
|
||||
/tmp/flux delete hr podinfo-helm --silent
|
||||
- name: flux delete helmrelease podinfo-git
|
||||
run: |
|
||||
./bin/flux delete hr podinfo-git --silent
|
||||
/tmp/flux delete hr podinfo-git --silent
|
||||
- name: flux delete source helm
|
||||
run: |
|
||||
./bin/flux delete source helm podinfo --silent
|
||||
/tmp/flux delete source helm podinfo --silent
|
||||
- name: flux delete source git
|
||||
run: |
|
||||
./bin/flux delete source git podinfo --silent
|
||||
/tmp/flux delete source git podinfo --silent
|
||||
- name: flux create tenant
|
||||
run: |
|
||||
./bin/flux create tenant dev-team --with-namespace=apps
|
||||
./bin/flux -n apps create source helm podinfo \
|
||||
/tmp/flux create tenant dev-team --with-namespace=apps
|
||||
/tmp/flux -n apps create source helm podinfo \
|
||||
--url https://stefanprodan.github.io/podinfo
|
||||
./bin/flux -n apps create hr podinfo-helm \
|
||||
/tmp/flux -n apps create hr podinfo-helm \
|
||||
--source=HelmRepository/podinfo \
|
||||
--chart=podinfo \
|
||||
--chart-version="5.0.x" \
|
||||
--service-account=dev-team
|
||||
- name: flux create image repository
|
||||
run: |
|
||||
./bin/flux create image repository podinfo \
|
||||
/tmp/flux create image repository podinfo \
|
||||
--image=ghcr.io/stefanprodan/podinfo \
|
||||
--interval=1m
|
||||
- name: flux create image policy
|
||||
run: |
|
||||
./bin/flux create image policy podinfo \
|
||||
/tmp/flux create image policy podinfo \
|
||||
--image-ref=podinfo \
|
||||
--interval=1m \
|
||||
--semver=5.0.x
|
||||
--select-semver=5.0.x
|
||||
- name: flux create image policy podinfo-select-alpha
|
||||
run: |
|
||||
/tmp/flux create image policy podinfo-alpha \
|
||||
--image-ref=podinfo \
|
||||
--interval=1m \
|
||||
--select-alpha=desc
|
||||
- name: flux get image policy
|
||||
run: |
|
||||
./bin/flux get image policy podinfo | grep '5.0.3'
|
||||
/tmp/flux get image policy podinfo | grep '5.0.3'
|
||||
- name: flux2-kustomize-helm-example
|
||||
run: |
|
||||
./bin/flux create source git flux-system \
|
||||
/tmp/flux create source git flux-system \
|
||||
--url=https://github.com/fluxcd/flux2-kustomize-helm-example \
|
||||
--branch=main
|
||||
./bin/flux create kustomization flux-system \
|
||||
/tmp/flux create kustomization flux-system \
|
||||
--source=flux-system \
|
||||
--path=./clusters/staging
|
||||
kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=2m
|
||||
- name: flux check
|
||||
run: |
|
||||
./bin/flux check
|
||||
/tmp/flux check
|
||||
- name: flux uninstall
|
||||
run: |
|
||||
./bin/flux uninstall --crds --silent --timeout=10m
|
||||
/tmp/flux uninstall --silent
|
||||
- name: Debug failure
|
||||
if: failure()
|
||||
run: |
|
||||
|
||||
25
.github/workflows/fossa.yml
vendored
25
.github/workflows/fossa.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: FOSSA
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: "^1.14.x"
|
||||
- name: Add GOPATH to GITHUB_ENV
|
||||
run: echo "GOPATH=$(go env GOPATH)" >>"$GITHUB_ENV"
|
||||
- name: Add GOPATH to GITHUB_PATH
|
||||
run: echo "$GOPATH/bin" >>"$GITHUB_PATH"
|
||||
- name: Run FOSSA scan and upload build data
|
||||
uses: fossa-contrib/fossa-action@v1
|
||||
with:
|
||||
# FOSSA Push-Only API Token
|
||||
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
||||
github-token: ${{ github.token }}
|
||||
@@ -2,9 +2,9 @@ name: rebase
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
types: [ opened ]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
rebase:
|
||||
39
.github/workflows/release.yaml
vendored
39
.github/workflows/release.yaml
vendored
@@ -2,8 +2,7 @@ name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
tags: [ 'v*' ]
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
@@ -16,7 +15,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15.x
|
||||
go-version: 1.16.x
|
||||
- name: Download release notes utility
|
||||
env:
|
||||
GH_REL_URL: https://github.com/buchanae/github-release-notes/releases/download/0.2.0/github-release-notes-linux-amd64-0.2.0.tar.gz
|
||||
@@ -29,38 +28,10 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg//actions/kustomize@main
|
||||
- name: Generate manifests tarball
|
||||
run: |
|
||||
mkdir -p ./output
|
||||
files=""
|
||||
|
||||
# build controllers
|
||||
for controller in ./manifests/bases/*/; do
|
||||
output_path="./output/$(basename $controller).yaml"
|
||||
echo "building $controller to $output_path"
|
||||
|
||||
kustomize build $controller > $output_path
|
||||
files+=" $(basename $output_path)"
|
||||
done
|
||||
|
||||
# build rbac
|
||||
rbac_path="./manifests/rbac"
|
||||
rbac_output_path="./output/rbac.yaml"
|
||||
echo "building $rbac_path to $rbac_output_path"
|
||||
kustomize build $rbac_path > $rbac_output_path
|
||||
files+=" $(basename $rbac_output_path)"
|
||||
|
||||
# build policies
|
||||
policies_path="./manifests/policies"
|
||||
policies_output_path="./output/policies.yaml"
|
||||
echo "building $policies_path to $policies_output_path"
|
||||
kustomize build $policies_path > $policies_output_path
|
||||
files+=" $(basename $policies_output_path)"
|
||||
|
||||
# create tarball
|
||||
cd ./output && tar -cvzf manifests.tar.gz $files
|
||||
- name: Generate install manifest
|
||||
- name: Generate manifests
|
||||
run: |
|
||||
make cmd/flux/manifests
|
||||
./manifests/scripts/bundle.sh "" ./output manifests.tar.gz
|
||||
kustomize build ./manifests/install > ./output/install.yaml
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v1
|
||||
|
||||
60
.github/workflows/scan.yaml
vendored
Normal file
60
.github/workflows/scan.yaml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '18 10 * * 3'
|
||||
|
||||
jobs:
|
||||
fossa:
|
||||
name: FOSSA
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run FOSSA scan and upload build data
|
||||
uses: fossa-contrib/fossa-action@v1
|
||||
with:
|
||||
# FOSSA Push-Only API Token
|
||||
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
snyk:
|
||||
name: Snyk
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg//actions/kustomize@main
|
||||
- name: Build manifests
|
||||
run: |
|
||||
make cmd/flux/manifests
|
||||
- name: Run Snyk to check for vulnerabilities
|
||||
uses: snyk/actions/golang@master
|
||||
continue-on-error: true
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
args: --sarif-file-output=snyk.sarif
|
||||
- name: Upload result to GitHub Code Scanning
|
||||
uses: github/codeql-action/upload-sarif@v1
|
||||
with:
|
||||
sarif_file: snyk.sarif
|
||||
|
||||
codeql:
|
||||
name: CodeQL
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: go
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
@@ -13,7 +13,10 @@ jobs:
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16.x
|
||||
- name: Update component versions
|
||||
id: update
|
||||
run: |
|
||||
@@ -21,13 +24,13 @@ jobs:
|
||||
|
||||
bump_version() {
|
||||
local RELEASE_VERSION=$(curl -s https://api.github.com/repos/fluxcd/$1/releases | jq -r 'sort_by(.published_at) | .[-1] | .tag_name')
|
||||
local CURRENT_VERSION=$(sed -n "s/.*$1\/archive\/\(.*\).zip.*/\1/p;n" manifests/bases/$1/kustomization.yaml)
|
||||
local CURRENT_VERSION=$(sed -n "s/.*$1\/releases\/download\/\(.*\)\/.*/\1/p;n" manifests/bases/$1/kustomization.yaml)
|
||||
|
||||
if [[ "${RELEASE_VERSION}" != "${CURRENT_VERSION}" ]]; then
|
||||
# bump kustomize
|
||||
sed -i "s/\($1\/archive\/\)v.*\(.zip\/\/$1-\).*\(\/config.*\)/\1${RELEASE_VERSION}\2${RELEASE_VERSION/v}\3/g" "manifests/bases/$1/kustomization.yaml"
|
||||
sed -i "s/\($1\/releases\/download\/\)v.*\(\/.*\)/\1${RELEASE_VERSION}\2/g" "manifests/bases/$1/kustomization.yaml"
|
||||
|
||||
if [[ ! -z $(go list -m all | grep "github.com/fluxcd/$1/api" | awk '{print $2}') ]]; then
|
||||
if [[ ! -z $(grep "github.com/fluxcd/$1/api" go.mod | awk '{print $2}') ]]; then
|
||||
# bump go mod
|
||||
go mod edit -require="github.com/fluxcd/$1/api@${RELEASE_VERSION}"
|
||||
fi
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -14,4 +14,5 @@
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
bin/
|
||||
output/
|
||||
output/
|
||||
cmd/flux/manifests/
|
||||
|
||||
@@ -20,6 +20,9 @@ builds:
|
||||
id: darwin
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- <<: *build_defaults
|
||||
id: windows
|
||||
goos:
|
||||
|
||||
@@ -48,11 +48,13 @@ you might want to take a look at the [introductory talk and demo](https://www.yo
|
||||
|
||||
This project is composed of:
|
||||
|
||||
- [/f/flux2](https://github.com/fluxcd/flux2): The Flux CLI
|
||||
- [/f/source-manager](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources
|
||||
- [/f/kustomize-controller](https://github.com/fluxcd/kustomize-controller): Kubernetes operator for building GitOps pipelines with Kustomize
|
||||
- [/f/helm-controller](https://github.com/fluxcd/helm-controller): Kubernetes operator for building GitOps pipelines with Helm
|
||||
- [/f/notification-controller](https://github.com/fluxcd/notification-controller): Kubernetes operator for handling inbound and outbound events
|
||||
- [flux2](https://github.com/fluxcd/flux2): The Flux CLI
|
||||
- [source-manager](https://github.com/fluxcd/source-controller): Kubernetes operator for managing sources (Git and Helm repositories, S3-compatible Buckets)
|
||||
- [kustomize-controller](https://github.com/fluxcd/kustomize-controller): Kubernetes operator for building GitOps pipelines with Kustomize
|
||||
- [helm-controller](https://github.com/fluxcd/helm-controller): Kubernetes operator for building GitOps pipelines with Helm
|
||||
- [notification-controller](https://github.com/fluxcd/notification-controller): Kubernetes operator for handling inbound and outbound events
|
||||
- [image-reflector-controller](https://github.com/fluxcd/image-reflector-controller): Kubernetes operator for scanning container registries
|
||||
- [image-automation-controller](https://github.com/fluxcd/image-automation-controller): Kubernetes operator for patches container image tags in Git
|
||||
|
||||
### Understanding the code
|
||||
|
||||
@@ -63,6 +65,12 @@ for source changes.
|
||||
|
||||
### How to run the test suite
|
||||
|
||||
Prerequisites:
|
||||
|
||||
* go >= 1.16
|
||||
* kubectl >= 1.18
|
||||
* kustomize >= 3.1
|
||||
|
||||
You can run the unit tests by simply doing
|
||||
|
||||
```bash
|
||||
|
||||
12
Makefile
12
Makefile
@@ -1,4 +1,7 @@
|
||||
VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | tr -d '"')
|
||||
EMBEDDED_MANIFESTS_TARGET=cmd/flux/manifests
|
||||
|
||||
rwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2)) $(filter $(subst *,%,$(2)),$(d)))
|
||||
|
||||
all: test build
|
||||
|
||||
@@ -11,10 +14,13 @@ fmt:
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
test: tidy fmt vet docs
|
||||
test: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet docs
|
||||
go test ./... -coverprofile cover.out
|
||||
|
||||
build:
|
||||
$(EMBEDDED_MANIFESTS_TARGET): $(call rwildcard,manifests/,*.yaml *.json)
|
||||
./manifests/scripts/bundle.sh
|
||||
|
||||
build: $(EMBEDDED_MANIFESTS_TARGET)
|
||||
CGO_ENABLED=0 go build -o ./bin/flux ./cmd/flux
|
||||
|
||||
install:
|
||||
@@ -22,7 +28,7 @@ install:
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
rm docs/cmd/*
|
||||
rm -rf docs/cmd/*
|
||||
mkdir -p ./docs/cmd && go run ./cmd/flux/ docgen
|
||||
|
||||
install-dev:
|
||||
|
||||
25
README.md
25
README.md
@@ -60,13 +60,12 @@ To get started with Flux, start [browsing the
|
||||
documentation](https://toolkit.fluxcd.io) or get started with one of
|
||||
the following guides:
|
||||
|
||||
- [Get started with Flux (deep dive)](https://toolkit.fluxcd.io/get-started/)
|
||||
- [Installation](https://toolkit.fluxcd.io/guides/installation/)
|
||||
- [Get started with Flux](https://toolkit.fluxcd.io/get-started/)
|
||||
- [Manage Helm Releases](https://toolkit.fluxcd.io/guides/helmreleases/)
|
||||
- [Setup Notifications](https://toolkit.fluxcd.io/guides/notifications/)
|
||||
- [Setup Webhook Receivers](https://toolkit.fluxcd.io/guides/webhook-receivers/)
|
||||
- [Automate image updates to Git](https://toolkit.fluxcd.io/guides/image-update/)
|
||||
- [Manage Kubernetes secrets with Mozilla SOPS](https://toolkit.fluxcd.io/guides/mozilla-sops/)
|
||||
|
||||
If you should 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/)**.
|
||||
|
||||
## GitOps Toolkit
|
||||
|
||||
@@ -96,17 +95,24 @@ guides](https://toolkit.fluxcd.io/dev-guides/source-watcher/).
|
||||
- [Provider CRD](https://toolkit.fluxcd.io/components/notification/provider/)
|
||||
- [Alert CRD](https://toolkit.fluxcd.io/components/notification/alert/)
|
||||
- [Receiver CRD](https://toolkit.fluxcd.io/components/notification/receiver/)
|
||||
|
||||
- [Image Automation Controllers](https://toolkit.fluxcd.io/components/image/controller/)
|
||||
- [ImageRepository CRD](https://toolkit.fluxcd.io/components/image/imagerepositories/)
|
||||
- [ImagePolicy CRD](https://toolkit.fluxcd.io/components/image/imagepolicies/)
|
||||
- [ImageUpdateAutomation CRD](https://toolkit.fluxcd.io/components/image/imageupdateautomations/)
|
||||
|
||||
## Community
|
||||
|
||||
Need help or want to contribute? Please see the links below. The Flux project is always looking for new contributors and there are a multitude of ways to get involved.
|
||||
Need help or want to contribute? Please see the links below. The Flux project is always looking for
|
||||
new contributors and there are a multitude of ways to get involved.
|
||||
|
||||
- Getting Started?
|
||||
- Look at our [Get Started guide](https://toolkit.fluxcd.io/get-started/) and give us feedback
|
||||
- Need help?
|
||||
- First: Ask questions on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions)
|
||||
- Second: Talk to us in the #flux channel on [CNCF Slack](https://slack.cncf.io/)
|
||||
- Please follow our [Support Guidelines](https://fluxcd.io/support/) (in short: be nice, be respectful of volunteers' time, understand that maintainers and contributors cannot respond to all DMs, and keep discussions in the public #flux channel as much as possible).
|
||||
- Please follow our [Support Guidelines](https://fluxcd.io/support/)
|
||||
(in short: be nice, be respectful of volunteers' time, understand that maintainers and
|
||||
contributors cannot respond to all DMs, and keep discussions in the public #flux channel as much as possible).
|
||||
- Have feature proposals or want to contribute?
|
||||
- Propose features on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions)
|
||||
- Join our upcoming dev meetings ([meeting access and agenda](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view))
|
||||
@@ -115,6 +121,7 @@ Need help or want to contribute? Please see the links below. The Flux project is
|
||||
|
||||
### Events
|
||||
|
||||
Check out our **[events calendar](https://fluxcd.io/community/#talks)**, both with upcoming talks you can attend or past events videos you can watch.
|
||||
Check out our **[events calendar](https://fluxcd.io/community/#talks)**,
|
||||
both with upcoming talks you can attend or past events videos you can watch.
|
||||
|
||||
We look forward to seeing you with us!
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
FROM stefanprodan/alpine-base:latest
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -10,15 +10,20 @@ Usage:
|
||||
run: flux -v
|
||||
```
|
||||
|
||||
This action places the `flux` binary inside your repository root under `bin/flux`.
|
||||
You should add `bin/flux` to your `.gitignore` file, as in the following example:
|
||||
|
||||
```gitignore
|
||||
# ignore flux binary
|
||||
bin/flux
|
||||
```
|
||||
|
||||
Note that this action can only be used on GitHub **Linux AMD64** runners.
|
||||
The latest stable version of the `flux` binary is downloaded from
|
||||
GitHub [releases](https://github.com/fluxcd/flux2/releases)
|
||||
and placed at `/usr/local/bin/flux`.
|
||||
|
||||
You can download a specific version with:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: Setup Flux CLI
|
||||
uses: fluxcd/flux2/action@main
|
||||
with:
|
||||
version: 0.8.0
|
||||
```
|
||||
|
||||
### Automate Flux updates
|
||||
|
||||
|
||||
@@ -1,15 +1,38 @@
|
||||
name: 'kustomize'
|
||||
description: 'A GitHub Action for running Flux commands'
|
||||
author: 'Flux project'
|
||||
name: Setup Flux CLI
|
||||
description: A GitHub Action for running Flux commands
|
||||
author: Stefan Prodan
|
||||
branding:
|
||||
icon: 'command'
|
||||
color: 'blue'
|
||||
color: blue
|
||||
icon: command
|
||||
inputs:
|
||||
version:
|
||||
description: 'strict semver'
|
||||
description: "Flux version e.g. 0.8.0 (defaults to latest stable release)"
|
||||
required: false
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
args:
|
||||
- ${{ inputs.version }}
|
||||
using: composite
|
||||
steps:
|
||||
- name: "Download flux binary to tmp"
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=${{ inputs.version }}
|
||||
|
||||
if [ -z $VERSION ]; then
|
||||
VERSION=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest -sL | grep tag_name | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-)
|
||||
fi
|
||||
|
||||
BIN_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_amd64.tar.gz"
|
||||
curl -sL ${BIN_URL} -o /tmp/flux.tar.gz
|
||||
mkdir -p /tmp/flux
|
||||
tar -C /tmp/flux/ -zxvf /tmp/flux.tar.gz
|
||||
- name: "Add flux binary to /usr/local/bin"
|
||||
shell: bash
|
||||
run: |
|
||||
sudo cp /tmp/flux/flux /usr/local/bin
|
||||
- name: "Cleanup tmp"
|
||||
shell: bash
|
||||
run: |
|
||||
rm -rf /tmp/flux/ /tmp/flux.tar.gz
|
||||
- name: "Verify correct installation of binary"
|
||||
shell: bash
|
||||
run: |
|
||||
flux -v
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2020 The Flux authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set -e
|
||||
|
||||
VERSION=$1
|
||||
|
||||
if [ -z $VERSION ]; then
|
||||
# Find latest release if no version is specified
|
||||
VERSION=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest -sL | grep tag_name | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-)
|
||||
fi
|
||||
|
||||
# Download linux binary
|
||||
BIN_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_amd64.tar.gz"
|
||||
curl -sL $BIN_URL | tar xz
|
||||
|
||||
# Copy binary to GitHub runner
|
||||
mkdir -p $GITHUB_WORKSPACE/bin
|
||||
mv ./flux $GITHUB_WORKSPACE/bin
|
||||
chmod +x $GITHUB_WORKSPACE/bin/flux
|
||||
|
||||
# Print version
|
||||
$GITHUB_WORKSPACE/bin/flux -v
|
||||
|
||||
# Add binary to GitHub runner path
|
||||
echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH
|
||||
echo "$RUNNER_WORKSPACE/$(basename $GITHUB_REPOSITORY)/bin" >> $GITHUB_PATH
|
||||
@@ -19,13 +19,11 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -36,7 +34,9 @@ import (
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
kus "github.com/fluxcd/flux2/pkg/manifestgen/kustomization"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
||||
"github.com/fluxcd/flux2/pkg/status"
|
||||
)
|
||||
|
||||
var bootstrapCmd = &cobra.Command{
|
||||
@@ -60,6 +60,7 @@ type bootstrapFlags struct {
|
||||
requiredComponents []string
|
||||
tokenAuth bool
|
||||
clusterDomain string
|
||||
tolerationKeys []string
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -69,8 +70,8 @@ const (
|
||||
var bootstrapArgs = NewBootstrapFlags()
|
||||
|
||||
func init() {
|
||||
bootstrapCmd.PersistentFlags().StringVarP(&bootstrapArgs.version, "version", "v", rootArgs.defaults.Version,
|
||||
"toolkit version")
|
||||
bootstrapCmd.PersistentFlags().StringVarP(&bootstrapArgs.version, "version", "v", "",
|
||||
"toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases")
|
||||
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, "components", rootArgs.defaults.Components,
|
||||
"list of components, accepts comma-separated values")
|
||||
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil,
|
||||
@@ -91,6 +92,8 @@ func init() {
|
||||
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.logLevel, "log-level", bootstrapArgs.logLevel.Description())
|
||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.manifestsPath, "manifests", "", "path to the manifest directory")
|
||||
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
|
||||
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.tolerationKeys, "toleration-keys", nil,
|
||||
"list of toleration keys used to schedule the components pods onto nodes with matching taints")
|
||||
bootstrapCmd.PersistentFlags().MarkHidden("manifests")
|
||||
bootstrapCmd.PersistentFlags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64")
|
||||
rootCmd.AddCommand(bootstrapCmd)
|
||||
@@ -123,6 +126,20 @@ func bootstrapValidate() error {
|
||||
}
|
||||
|
||||
func generateInstallManifests(targetPath, namespace, tmpDir string, localManifests string) (string, error) {
|
||||
if ver, err := getVersion(bootstrapArgs.version); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
bootstrapArgs.version = ver
|
||||
}
|
||||
|
||||
manifestsBase := ""
|
||||
if isEmbeddedVersion(bootstrapArgs.version) {
|
||||
if err := writeEmbeddedManifests(tmpDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
manifestsBase = tmpDir
|
||||
}
|
||||
|
||||
opts := install.Options{
|
||||
BaseURL: localManifests,
|
||||
Version: bootstrapArgs.version,
|
||||
@@ -138,13 +155,14 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes
|
||||
Timeout: rootArgs.timeout,
|
||||
TargetPath: targetPath,
|
||||
ClusterDomain: bootstrapArgs.clusterDomain,
|
||||
TolerationKeys: bootstrapArgs.tolerationKeys,
|
||||
}
|
||||
|
||||
if localManifests == "" {
|
||||
opts.BaseURL = rootArgs.defaults.BaseURL
|
||||
}
|
||||
|
||||
output, err := install.Generate(opts)
|
||||
output, err := install.Generate(opts, manifestsBase)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("generating install manifests failed: %w", err)
|
||||
}
|
||||
@@ -159,19 +177,24 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes
|
||||
func applyInstallManifests(ctx context.Context, manifestPath string, components []string) error {
|
||||
kubectlArgs := []string{"apply", "-f", manifestPath}
|
||||
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
|
||||
return fmt.Errorf("install failed")
|
||||
return fmt.Errorf("install failed: %w", err)
|
||||
}
|
||||
|
||||
statusChecker, err := NewStatusChecker(time.Second, time.Minute)
|
||||
kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install failed: %w", err)
|
||||
}
|
||||
statusChecker, err := status.NewStatusChecker(kubeConfig, time.Second, rootArgs.timeout, logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install failed: %w", err)
|
||||
}
|
||||
componentRefs, err := buildComponentObjectRefs(components...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install failed: %w", err)
|
||||
}
|
||||
|
||||
logger.Waitingf("verifying installation")
|
||||
if err := statusChecker.Assess(components...); err != nil {
|
||||
if err := statusChecker.Assess(componentRefs...); err != nil {
|
||||
return fmt.Errorf("install failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -182,6 +205,7 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
|
||||
URL: url,
|
||||
Branch: branch,
|
||||
Interval: interval,
|
||||
Secret: namespace,
|
||||
TargetPath: targetPath,
|
||||
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
|
||||
}
|
||||
@@ -196,9 +220,19 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
|
||||
return "", err
|
||||
}
|
||||
outputDir := filepath.Dir(output)
|
||||
if err := utils.GenerateKustomizationYaml(outputDir); err != nil {
|
||||
|
||||
kusOpts := kus.MakeDefaultOptions()
|
||||
kusOpts.BaseDir = tmpDir
|
||||
kusOpts.TargetPath = filepath.Dir(manifest.Path)
|
||||
|
||||
kustomization, err := kus.Generate(kusOpts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err = kustomization.WriteFile(tmpDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return outputDir, nil
|
||||
}
|
||||
|
||||
@@ -251,35 +285,6 @@ func shouldCreateDeployKey(ctx context.Context, kubeClient client.Client, namesp
|
||||
return false
|
||||
}
|
||||
|
||||
func generateDeployKey(ctx context.Context, kubeClient client.Client, url *url.URL, namespace string) (string, error) {
|
||||
pair, err := generateKeyPair(ctx, sourceArgs.GitKeyAlgorithm, sourceArgs.GitRSABits, sourceArgs.GitECDSACurve)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hostKey, err := scanHostKey(ctx, url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
Namespace: namespace,
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"identity": string(pair.PrivateKey),
|
||||
"identity.pub": string(pair.PublicKey),
|
||||
"known_hosts": string(hostKey),
|
||||
},
|
||||
}
|
||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(pair.PublicKey), nil
|
||||
}
|
||||
|
||||
func checkIfBootstrapPathDiffers(ctx context.Context, kubeClient client.Client, namespace string, path string) (string, bool) {
|
||||
namespacedName := types.NamespacedName{
|
||||
Name: namespace,
|
||||
|
||||
@@ -26,14 +26,14 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||
)
|
||||
|
||||
var bootstrapGitHubCmd = &cobra.Command{
|
||||
@@ -80,7 +80,6 @@ type githubFlags struct {
|
||||
hostname string
|
||||
path flags.SafeRelativePath
|
||||
teams []string
|
||||
delete bool
|
||||
sshHostname string
|
||||
}
|
||||
|
||||
@@ -94,16 +93,13 @@ func init() {
|
||||
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.owner, "owner", "", "GitHub user or organization name")
|
||||
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.repository, "repository", "", "GitHub repository name")
|
||||
bootstrapGitHubCmd.Flags().StringArrayVar(&githubArgs.teams, "team", []string{}, "GitHub team to be given maintainer access")
|
||||
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.personal, "personal", false, "is personal repository")
|
||||
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.private, "private", true, "is private repository")
|
||||
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.personal, "personal", false, "if true, the owner is assumed to be a GitHub user; otherwise an org")
|
||||
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.private, "private", true, "if true, the repository is assumed to be private")
|
||||
bootstrapGitHubCmd.Flags().DurationVar(&githubArgs.interval, "interval", time.Minute, "sync interval")
|
||||
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.hostname, "hostname", git.GitHubDefaultHostname, "GitHub hostname")
|
||||
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.sshHostname, "ssh-hostname", "", "GitHub SSH hostname, to be used when the SSH host differs from the HTTPS one")
|
||||
bootstrapGitHubCmd.Flags().Var(&githubArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
|
||||
|
||||
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.delete, "delete", false, "delete repository (used for testing only)")
|
||||
bootstrapGitHubCmd.Flags().MarkHidden("delete")
|
||||
|
||||
bootstrapCmd.AddCommand(bootstrapGitHubCmd)
|
||||
}
|
||||
|
||||
@@ -125,13 +121,25 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
usedPath, bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, rootArgs.namespace, filepath.ToSlash(githubArgs.path.String()))
|
||||
usedPath, bootstrapPathDiffers := checkIfBootstrapPathDiffers(
|
||||
ctx,
|
||||
kubeClient,
|
||||
rootArgs.namespace,
|
||||
filepath.ToSlash(githubArgs.path.String()),
|
||||
)
|
||||
|
||||
if bootstrapPathDiffers {
|
||||
return fmt.Errorf("cluster already bootstrapped to %v path", usedPath)
|
||||
}
|
||||
|
||||
repository, err := git.NewRepository(githubArgs.repository, githubArgs.owner, githubArgs.hostname, ghToken, "flux", githubArgs.owner+"@users.noreply.github.com")
|
||||
repository, err := git.NewRepository(
|
||||
githubArgs.repository,
|
||||
githubArgs.owner,
|
||||
githubArgs.hostname,
|
||||
ghToken,
|
||||
"flux",
|
||||
githubArgs.owner+"@users.noreply.github.com",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -151,14 +159,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
if githubArgs.delete {
|
||||
if err := provider.DeleteRepository(ctx, repository); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Successf("repository deleted")
|
||||
return nil
|
||||
}
|
||||
|
||||
// create GitHub repository if doesn't exists
|
||||
logger.Actionf("connecting to %s", githubArgs.hostname)
|
||||
changed, err := provider.CreateRepository(ctx, repository)
|
||||
@@ -190,13 +190,22 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// generate install manifests
|
||||
logger.Generatef("generating manifests")
|
||||
installManifest, err := generateInstallManifests(githubArgs.path.String(), rootArgs.namespace, tmpDir, bootstrapArgs.manifestsPath)
|
||||
installManifest, err := generateInstallManifests(
|
||||
githubArgs.path.String(),
|
||||
rootArgs.namespace,
|
||||
tmpDir,
|
||||
bootstrapArgs.manifestsPath,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// stage install manifests
|
||||
changed, err = repository.Commit(ctx, path.Join(githubArgs.path.String(), rootArgs.namespace), "Add manifests")
|
||||
changed, err = repository.Commit(
|
||||
ctx,
|
||||
path.Join(githubArgs.path.String(), rootArgs.namespace),
|
||||
fmt.Sprintf("Add flux %s components manifests", bootstrapArgs.version),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -223,44 +232,48 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
||||
logger.Successf("install completed")
|
||||
}
|
||||
|
||||
repoURL := repository.GetURL()
|
||||
|
||||
repoURL := repository.GetSSH()
|
||||
secretOpts := sourcesecret.Options{
|
||||
Name: rootArgs.namespace,
|
||||
Namespace: rootArgs.namespace,
|
||||
}
|
||||
if bootstrapArgs.tokenAuth {
|
||||
// setup HTTPS token auth
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: rootArgs.namespace,
|
||||
Namespace: rootArgs.namespace,
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"username": "git",
|
||||
"password": ghToken,
|
||||
},
|
||||
// Setup HTTPS token auth
|
||||
repoURL = repository.GetURL()
|
||||
secretOpts.Username = "git"
|
||||
secretOpts.Password = ghToken
|
||||
} else if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
|
||||
// Setup SSH auth
|
||||
u, err := url.Parse(repoURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git URL parse failed: %w", err)
|
||||
}
|
||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
||||
secretOpts.SSHHostname = u.Host
|
||||
secretOpts.PrivateKeyAlgorithm = sourcesecret.RSAPrivateKeyAlgorithm
|
||||
secretOpts.RSAKeyBits = 2048
|
||||
}
|
||||
|
||||
secret, err := sourcesecret.Generate(secretOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var s corev1.Secret
|
||||
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s.StringData) > 0 {
|
||||
logger.Actionf("configuring deploy key")
|
||||
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// setup SSH deploy key
|
||||
repoURL = repository.GetSSH()
|
||||
if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
|
||||
logger.Actionf("configuring deploy key")
|
||||
u, err := url.Parse(repository.GetSSH())
|
||||
if err != nil {
|
||||
return fmt.Errorf("git URL parse failed: %w", err)
|
||||
}
|
||||
|
||||
key, err := generateDeployKey(ctx, kubeClient, u, rootArgs.namespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating deploy key failed: %w", err)
|
||||
}
|
||||
|
||||
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
|
||||
keyName := "flux"
|
||||
if githubArgs.path != "" {
|
||||
keyName = fmt.Sprintf("flux-%s", githubArgs.path)
|
||||
}
|
||||
|
||||
if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
|
||||
if changed, err := provider.AddDeployKey(ctx, repository, ppk, keyName); err != nil {
|
||||
return err
|
||||
} else if changed {
|
||||
logger.Successf("deploy key configured")
|
||||
@@ -270,13 +283,25 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// configure repo synchronization
|
||||
logger.Actionf("generating sync manifests")
|
||||
syncManifests, err := generateSyncManifests(repoURL, bootstrapArgs.branch, rootArgs.namespace, rootArgs.namespace, filepath.ToSlash(githubArgs.path.String()), tmpDir, githubArgs.interval)
|
||||
syncManifests, err := generateSyncManifests(
|
||||
repoURL,
|
||||
bootstrapArgs.branch,
|
||||
rootArgs.namespace,
|
||||
rootArgs.namespace,
|
||||
filepath.ToSlash(githubArgs.path.String()),
|
||||
tmpDir,
|
||||
githubArgs.interval,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// commit and push manifests
|
||||
if changed, err = repository.Commit(ctx, path.Join(githubArgs.path.String(), rootArgs.namespace), "Add manifests"); err != nil {
|
||||
if changed, err = repository.Commit(
|
||||
ctx,
|
||||
path.Join(githubArgs.path.String(), rootArgs.namespace),
|
||||
fmt.Sprintf("Add flux %s sync manifests", bootstrapArgs.version),
|
||||
); err != nil {
|
||||
return err
|
||||
} else if changed {
|
||||
if err := repository.Push(ctx); err != nil {
|
||||
|
||||
@@ -29,12 +29,13 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/fluxcd/pkg/git"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||
)
|
||||
|
||||
var bootstrapGitLabCmd = &cobra.Command{
|
||||
@@ -89,8 +90,8 @@ var gitlabArgs gitlabFlags
|
||||
func init() {
|
||||
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.owner, "owner", "", "GitLab user or group name")
|
||||
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.repository, "repository", "", "GitLab repository name")
|
||||
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.personal, "personal", false, "is personal repository")
|
||||
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.private, "private", true, "is private repository")
|
||||
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.personal, "personal", false, "if true, the owner is assumed to be a GitLab user; otherwise a group")
|
||||
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.private, "private", true, "if true, the repository is assumed to be private")
|
||||
bootstrapGitLabCmd.Flags().DurationVar(&gitlabArgs.interval, "interval", time.Minute, "sync interval")
|
||||
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.hostname, "hostname", git.GitLabDefaultHostname, "GitLab hostname")
|
||||
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.sshHostname, "ssh-hostname", "", "GitLab SSH hostname, to be used when the SSH host differs from the HTTPS one")
|
||||
@@ -131,7 +132,14 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("cluster already bootstrapped to %v path", usedPath)
|
||||
}
|
||||
|
||||
repository, err := git.NewRepository(gitlabArgs.repository, gitlabArgs.owner, gitlabArgs.hostname, glToken, "flux", gitlabArgs.owner+"@users.noreply.gitlab.com")
|
||||
repository, err := git.NewRepository(
|
||||
gitlabArgs.repository,
|
||||
gitlabArgs.owner,
|
||||
gitlabArgs.hostname,
|
||||
glToken,
|
||||
"flux",
|
||||
gitlabArgs.owner+"@users.noreply.gitlab.com",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -169,13 +177,22 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// generate install manifests
|
||||
logger.Generatef("generating manifests")
|
||||
installManifest, err := generateInstallManifests(gitlabArgs.path.String(), rootArgs.namespace, tmpDir, bootstrapArgs.manifestsPath)
|
||||
installManifest, err := generateInstallManifests(
|
||||
gitlabArgs.path.String(),
|
||||
rootArgs.namespace,
|
||||
tmpDir,
|
||||
bootstrapArgs.manifestsPath,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// stage install manifests
|
||||
changed, err = repository.Commit(ctx, path.Join(gitlabArgs.path.String(), rootArgs.namespace), "Add manifests")
|
||||
changed, err = repository.Commit(
|
||||
ctx,
|
||||
path.Join(gitlabArgs.path.String(), rootArgs.namespace),
|
||||
fmt.Sprintf("Add flux %s components manifests", bootstrapArgs.version),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -202,44 +219,48 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
||||
logger.Successf("install completed")
|
||||
}
|
||||
|
||||
repoURL := repository.GetURL()
|
||||
|
||||
repoURL := repository.GetSSH()
|
||||
secretOpts := sourcesecret.Options{
|
||||
Name: rootArgs.namespace,
|
||||
Namespace: rootArgs.namespace,
|
||||
}
|
||||
if bootstrapArgs.tokenAuth {
|
||||
// setup HTTPS token auth
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: rootArgs.namespace,
|
||||
Namespace: rootArgs.namespace,
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"username": "git",
|
||||
"password": glToken,
|
||||
},
|
||||
// Setup HTTPS token auth
|
||||
repoURL = repository.GetURL()
|
||||
secretOpts.Username = "git"
|
||||
secretOpts.Password = glToken
|
||||
} else if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
|
||||
// Setup SSH auth
|
||||
u, err := url.Parse(repoURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git URL parse failed: %w", err)
|
||||
}
|
||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
||||
secretOpts.SSHHostname = u.Host
|
||||
secretOpts.PrivateKeyAlgorithm = sourcesecret.RSAPrivateKeyAlgorithm
|
||||
secretOpts.RSAKeyBits = 2048
|
||||
}
|
||||
|
||||
secret, err := sourcesecret.Generate(secretOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var s corev1.Secret
|
||||
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s.StringData) > 0 {
|
||||
logger.Actionf("configuring deploy key")
|
||||
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// setup SSH deploy key
|
||||
repoURL = repository.GetSSH()
|
||||
if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
|
||||
logger.Actionf("configuring deploy key")
|
||||
u, err := url.Parse(repoURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git URL parse failed: %w", err)
|
||||
}
|
||||
|
||||
key, err := generateDeployKey(ctx, kubeClient, u, rootArgs.namespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating deploy key failed: %w", err)
|
||||
}
|
||||
|
||||
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
|
||||
keyName := "flux"
|
||||
if gitlabArgs.path != "" {
|
||||
keyName = fmt.Sprintf("flux-%s", gitlabArgs.path)
|
||||
}
|
||||
|
||||
if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
|
||||
if changed, err := provider.AddDeployKey(ctx, repository, ppk, keyName); err != nil {
|
||||
return err
|
||||
} else if changed {
|
||||
logger.Successf("deploy key configured")
|
||||
@@ -249,13 +270,25 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// configure repo synchronization
|
||||
logger.Actionf("generating sync manifests")
|
||||
syncManifests, err := generateSyncManifests(repoURL, bootstrapArgs.branch, rootArgs.namespace, rootArgs.namespace, filepath.ToSlash(gitlabArgs.path.String()), tmpDir, gitlabArgs.interval)
|
||||
syncManifests, err := generateSyncManifests(
|
||||
repoURL,
|
||||
bootstrapArgs.branch,
|
||||
rootArgs.namespace,
|
||||
rootArgs.namespace,
|
||||
filepath.ToSlash(gitlabArgs.path.String()),
|
||||
tmpDir,
|
||||
gitlabArgs.interval,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// commit and push manifests
|
||||
if changed, err = repository.Commit(ctx, path.Join(gitlabArgs.path.String(), rootArgs.namespace), "Add manifests"); err != nil {
|
||||
if changed, err = repository.Commit(
|
||||
ctx,
|
||||
path.Join(gitlabArgs.path.String(), rootArgs.namespace),
|
||||
fmt.Sprintf("Add flux %s sync manifests", bootstrapArgs.version),
|
||||
); err != nil {
|
||||
return err
|
||||
} else if changed {
|
||||
if err := repository.Push(ctx); err != nil {
|
||||
|
||||
@@ -21,14 +21,20 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/spf13/cobra"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/pkg/version"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
"github.com/fluxcd/flux2/pkg/status"
|
||||
)
|
||||
|
||||
var checkCmd = &cobra.Command{
|
||||
@@ -74,11 +80,13 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
|
||||
logger.Actionf("checking prerequisites")
|
||||
checkFailed := false
|
||||
|
||||
if !kubectlCheck(ctx, ">=1.18.0") {
|
||||
fluxCheck()
|
||||
|
||||
if !kubectlCheck(ctx, ">=1.18.0-0") {
|
||||
checkFailed = true
|
||||
}
|
||||
|
||||
if !kubernetesCheck(">=1.16.0") {
|
||||
if !kubernetesCheck(">=1.16.0-0") {
|
||||
checkFailed = true
|
||||
}
|
||||
|
||||
@@ -101,7 +109,29 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func kubectlCheck(ctx context.Context, version string) bool {
|
||||
func fluxCheck() {
|
||||
curSv, err := version.ParseVersion(VERSION)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Exclude development builds.
|
||||
if curSv.Prerelease() != "" {
|
||||
return
|
||||
}
|
||||
latest, err := install.GetLatestVersion()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
latestSv, err := version.ParseVersion(latest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if latestSv.GreaterThan(curSv) {
|
||||
logger.Failuref("flux %s <%s (new version is available, please upgrade)", curSv, latestSv)
|
||||
}
|
||||
}
|
||||
|
||||
func kubectlCheck(ctx context.Context, constraint string) bool {
|
||||
_, err := exec.LookPath("kubectl")
|
||||
if err != nil {
|
||||
logger.Failuref("kubectl not found")
|
||||
@@ -117,58 +147,58 @@ func kubectlCheck(ctx context.Context, version string) bool {
|
||||
|
||||
kv := &kubectlVersion{}
|
||||
if err = json.Unmarshal([]byte(output), kv); err != nil {
|
||||
logger.Failuref("kubectl version output can't be unmarshaled")
|
||||
logger.Failuref("kubectl version output can't be unmarshalled")
|
||||
return false
|
||||
}
|
||||
|
||||
v, err := semver.ParseTolerant(kv.ClientVersion.GitVersion)
|
||||
v, err := version.ParseVersion(kv.ClientVersion.GitVersion)
|
||||
if err != nil {
|
||||
logger.Failuref("kubectl version can't be parsed")
|
||||
return false
|
||||
}
|
||||
|
||||
rng, _ := semver.ParseRange(version)
|
||||
if !rng(v) {
|
||||
logger.Failuref("kubectl version must be %s", version)
|
||||
c, _ := semver.NewConstraint(constraint)
|
||||
if !c.Check(v) {
|
||||
logger.Failuref("kubectl version %s < %s", v.Original(), constraint)
|
||||
return false
|
||||
}
|
||||
|
||||
logger.Successf("kubectl %s %s", v.String(), version)
|
||||
logger.Successf("kubectl %s %s", v.String(), constraint)
|
||||
return true
|
||||
}
|
||||
|
||||
func kubernetesCheck(version string) bool {
|
||||
func kubernetesCheck(constraint string) bool {
|
||||
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(cfg)
|
||||
clientSet, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
ver, err := client.Discovery().ServerVersion()
|
||||
kv, err := clientSet.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
logger.Failuref("Kubernetes API call failed: %s", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
v, err := semver.ParseTolerant(ver.String())
|
||||
v, err := version.ParseVersion(kv.String())
|
||||
if err != nil {
|
||||
logger.Failuref("Kubernetes version can't be determined")
|
||||
return false
|
||||
}
|
||||
|
||||
rng, _ := semver.ParseRange(version)
|
||||
if !rng(v) {
|
||||
logger.Failuref("Kubernetes version must be %s", version)
|
||||
c, _ := semver.NewConstraint(constraint)
|
||||
if !c.Check(v) {
|
||||
logger.Failuref("Kubernetes version %s < %s", v.Original(), constraint)
|
||||
return false
|
||||
}
|
||||
|
||||
logger.Successf("Kubernetes %s %s", v.String(), version)
|
||||
logger.Successf("Kubernetes %s %s", v.String(), constraint)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -176,23 +206,34 @@ func componentsCheck() bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
statusChecker, err := NewStatusChecker(time.Second, 30*time.Second)
|
||||
kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
statusChecker, err := status.NewStatusChecker(kubeConfig, time.Second, rootArgs.timeout, logger)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ok := true
|
||||
deployments := append(checkArgs.components, checkArgs.extraComponents...)
|
||||
for _, deployment := range deployments {
|
||||
if err := statusChecker.Assess(deployment); err != nil {
|
||||
ok = false
|
||||
} else {
|
||||
logger.Successf("%s: healthy", deployment)
|
||||
}
|
||||
|
||||
kubectlArgs := []string{"-n", rootArgs.namespace, "get", "deployment", deployment, "-o", "jsonpath=\"{..image}\""}
|
||||
if output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err == nil {
|
||||
logger.Actionf(strings.TrimPrefix(strings.TrimSuffix(output, "\""), "\""))
|
||||
selector := client.MatchingLabels{"app.kubernetes.io/instance": rootArgs.namespace}
|
||||
var list v1.DeploymentList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace), selector); err == nil {
|
||||
for _, d := range list.Items {
|
||||
if ref, err := buildComponentObjectRefs(d.Name); err == nil {
|
||||
if err := statusChecker.Assess(ref...); err != nil {
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
for _, c := range d.Spec.Template.Spec.Containers {
|
||||
logger.Actionf(c.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok
|
||||
|
||||
@@ -25,13 +25,9 @@ import (
|
||||
var completionFishCmd = &cobra.Command{
|
||||
Use: "fish",
|
||||
Short: "Generates fish completion scripts",
|
||||
Example: `To load completion run
|
||||
Example: `To configure your fish shell to load completions for each session write this script to your completions dir:
|
||||
|
||||
. <(flux completion fish)
|
||||
|
||||
To configure your fish shell to load completions for each session write this script to your completions dir:
|
||||
|
||||
flux completion fish > ~/.config/fish/completions/flux
|
||||
flux completion fish > ~/.config/fish/completions/flux.fish
|
||||
|
||||
See http://fishshell.com/docs/current/index.html#completion-own for more details
|
||||
`,
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/runtime/transform"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
@@ -198,7 +199,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if valuesMap == nil {
|
||||
valuesMap = jsonMap
|
||||
} else {
|
||||
valuesMap = utils.MergeMaps(valuesMap, jsonMap)
|
||||
valuesMap = transform.MergeMaps(valuesMap, jsonMap)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp/syntax"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -28,7 +32,7 @@ import (
|
||||
)
|
||||
|
||||
var createImagePolicyCmd = &cobra.Command{
|
||||
Use: "policy <name>",
|
||||
Use: "policy [name]",
|
||||
Short: "Create or update an ImagePolicy object",
|
||||
Long: `The create image policy command generates an ImagePolicy resource.
|
||||
An ImagePolicy object calculates a "latest image" given an image
|
||||
@@ -36,12 +40,28 @@ repository and a policy, e.g., semver.
|
||||
|
||||
The image that sorts highest according to the policy is recorded in
|
||||
the status of the object.`,
|
||||
Example: ` # Create an ImagePolicy to select the latest stable release
|
||||
flux create image policy podinfo \
|
||||
--image-ref=podinfo \
|
||||
--select-semver=">=1.0.0"
|
||||
|
||||
# Create an ImagePolicy to select the latest main branch build tagged as "${GIT_BRANCH}-${GIT_SHA:0:7}-$(date +%s)"
|
||||
flux create image policy podinfo \
|
||||
--image-ref=podinfo \
|
||||
--select-numeric=asc \
|
||||
--filter-regex='^main-[a-f0-9]+-(?P<ts>[0-9]+)' \
|
||||
--filter-extract='$ts'
|
||||
`,
|
||||
RunE: createImagePolicyRun}
|
||||
|
||||
type imagePolicyFlags struct {
|
||||
imageRef string
|
||||
semver string
|
||||
filterRegex string
|
||||
imageRef string
|
||||
semver string
|
||||
alpha string
|
||||
numeric string
|
||||
filterRegex string
|
||||
filterExtract string
|
||||
filterNumerical string
|
||||
}
|
||||
|
||||
var imagePolicyArgs = imagePolicyFlags{}
|
||||
@@ -49,8 +69,11 @@ var imagePolicyArgs = imagePolicyFlags{}
|
||||
func init() {
|
||||
flags := createImagePolicyCmd.Flags()
|
||||
flags.StringVar(&imagePolicyArgs.imageRef, "image-ref", "", "the name of an image repository object")
|
||||
flags.StringVar(&imagePolicyArgs.semver, "semver", "", "a semver range to apply to tags; e.g., '1.x'")
|
||||
flags.StringVar(&imagePolicyArgs.filterRegex, "filter-regex", "", " regular expression pattern used to filter the image tags")
|
||||
flags.StringVar(&imagePolicyArgs.semver, "select-semver", "", "a semver range to apply to tags; e.g., '1.x'")
|
||||
flags.StringVar(&imagePolicyArgs.alpha, "select-alpha", "", "use alphabetical sorting to select image; either \"asc\" meaning select the last, or \"desc\" meaning select the first")
|
||||
flags.StringVar(&imagePolicyArgs.numeric, "select-numeric", "", "use numeric sorting to select image; either \"asc\" meaning select the last, or \"desc\" meaning select the first")
|
||||
flags.StringVar(&imagePolicyArgs.filterRegex, "filter-regex", "", "regular expression pattern used to filter the image tags")
|
||||
flags.StringVar(&imagePolicyArgs.filterExtract, "filter-extract", "", "replacement pattern (using capture groups from --filter-regex) to use for sorting")
|
||||
|
||||
createImageCmd.AddCommand(createImagePolicyCmd)
|
||||
}
|
||||
@@ -90,18 +113,49 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
switch {
|
||||
case imagePolicyArgs.semver != "" && imagePolicyArgs.alpha != "":
|
||||
case imagePolicyArgs.semver != "" && imagePolicyArgs.numeric != "":
|
||||
case imagePolicyArgs.alpha != "" && imagePolicyArgs.numeric != "":
|
||||
return fmt.Errorf("only one of --select-semver, --select-alpha or --select-numeric can be specified")
|
||||
case imagePolicyArgs.semver != "":
|
||||
policy.Spec.Policy.SemVer = &imagev1.SemVerPolicy{
|
||||
Range: imagePolicyArgs.semver,
|
||||
}
|
||||
case imagePolicyArgs.alpha != "":
|
||||
if imagePolicyArgs.alpha != "desc" && imagePolicyArgs.alpha != "asc" {
|
||||
return fmt.Errorf("--select-alpha must be one of [\"asc\", \"desc\"]")
|
||||
}
|
||||
policy.Spec.Policy.Alphabetical = &imagev1.AlphabeticalPolicy{
|
||||
Order: imagePolicyArgs.alpha,
|
||||
}
|
||||
case imagePolicyArgs.numeric != "":
|
||||
if imagePolicyArgs.numeric != "desc" && imagePolicyArgs.numeric != "asc" {
|
||||
return fmt.Errorf("--select-numeric must be one of [\"asc\", \"desc\"]")
|
||||
}
|
||||
policy.Spec.Policy.Numerical = &imagev1.NumericalPolicy{
|
||||
Order: imagePolicyArgs.numeric,
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("a policy must be provided with --semver")
|
||||
return fmt.Errorf("a policy must be provided with either --select-semver or --select-alpha")
|
||||
}
|
||||
|
||||
if imagePolicyArgs.filterRegex != "" {
|
||||
exp, err := syntax.Parse(imagePolicyArgs.filterRegex, syntax.Perl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("--filter-regex is an invalid regex pattern")
|
||||
}
|
||||
policy.Spec.FilterTags = &imagev1.TagFilter{
|
||||
Pattern: imagePolicyArgs.filterRegex,
|
||||
}
|
||||
|
||||
if imagePolicyArgs.filterExtract != "" {
|
||||
if err := validateExtractStr(imagePolicyArgs.filterExtract, exp.CapNames()); err != nil {
|
||||
return err
|
||||
}
|
||||
policy.Spec.FilterTags.Extract = imagePolicyArgs.filterExtract
|
||||
}
|
||||
} else if imagePolicyArgs.filterExtract != "" {
|
||||
return fmt.Errorf("cannot specify --filter-extract without specifying --filter-regex")
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
@@ -117,3 +171,94 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Performs a dry-run of the extract function in Regexp to validate the template
|
||||
func validateExtractStr(template string, capNames []string) error {
|
||||
for len(template) > 0 {
|
||||
i := strings.Index(template, "$")
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
template = template[i:]
|
||||
if len(template) > 1 && template[1] == '$' {
|
||||
template = template[2:]
|
||||
continue
|
||||
}
|
||||
name, num, rest, ok := extract(template)
|
||||
if !ok {
|
||||
// Malformed extract string, assume user didn't want this
|
||||
template = template[1:]
|
||||
return fmt.Errorf("--filter-extract is malformed")
|
||||
}
|
||||
template = rest
|
||||
if num >= 0 {
|
||||
// we won't worry about numbers as we can't validate these
|
||||
continue
|
||||
} else {
|
||||
found := false
|
||||
for _, capName := range capNames {
|
||||
if name == capName {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("capture group $%s used in --filter-extract not found in --filter-regex", name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// extract method from the regexp package
|
||||
// returns the name or number of the value prepended by $
|
||||
func extract(str string) (name string, num int, rest string, ok bool) {
|
||||
if len(str) < 2 || str[0] != '$' {
|
||||
return
|
||||
}
|
||||
brace := false
|
||||
if str[1] == '{' {
|
||||
brace = true
|
||||
str = str[2:]
|
||||
} else {
|
||||
str = str[1:]
|
||||
}
|
||||
i := 0
|
||||
for i < len(str) {
|
||||
rune, size := utf8.DecodeRuneInString(str[i:])
|
||||
if !unicode.IsLetter(rune) && !unicode.IsDigit(rune) && rune != '_' {
|
||||
break
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if i == 0 {
|
||||
// empty name is not okay
|
||||
return
|
||||
}
|
||||
name = str[:i]
|
||||
if brace {
|
||||
if i >= len(str) || str[i] != '}' {
|
||||
// missing closing brace
|
||||
return
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
// Parse number.
|
||||
num = 0
|
||||
for i := 0; i < len(name); i++ {
|
||||
if name[i] < '0' || '9' < name[i] || num >= 1e8 {
|
||||
num = -1
|
||||
break
|
||||
}
|
||||
num = num*10 + int(name[i]) - '0'
|
||||
}
|
||||
// Disallow leading zeros.
|
||||
if name[0] == '0' && len(name) > 1 {
|
||||
num = -1
|
||||
}
|
||||
|
||||
rest = str[i:]
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
)
|
||||
|
||||
var createImageRepositoryCmd = &cobra.Command{
|
||||
Use: "repository <name>",
|
||||
Use: "repository [name]",
|
||||
Short: "Create or update an ImageRepository object",
|
||||
Long: `The create image repository command generates an ImageRepository resource.
|
||||
An ImageRepository object specifies an image repository to scan.`,
|
||||
|
||||
@@ -28,19 +28,38 @@ import (
|
||||
)
|
||||
|
||||
var createImageUpdateCmd = &cobra.Command{
|
||||
Use: "update <name>",
|
||||
Use: "update [name]",
|
||||
Short: "Create or update an ImageUpdateAutomation object",
|
||||
Long: `The create image update command generates an ImageUpdateAutomation resource.
|
||||
An ImageUpdateAutomation object specifies an automated update to images
|
||||
mentioned in YAMLs in a git repository.`,
|
||||
Example: ` # Configure image updates for the main repository created by flux bootstrap
|
||||
flux create image update flux-system \
|
||||
--git-repo-ref=flux-system \
|
||||
--git-repo-path="./clusters/my-cluster" \
|
||||
--checkout-branch=main \
|
||||
--author-name=flux \
|
||||
--author-email=flux@example.com \
|
||||
--commit-template="{{range .Updated.Images}}{{println .}}{{end}}"
|
||||
|
||||
# Configure image updates to push changes to a different branch, if the branch doesn't exists it will be created
|
||||
flux create image update flux-system \
|
||||
--git-repo-ref=flux-system \
|
||||
--git-repo-path="./clusters/my-cluster" \
|
||||
--checkout-branch=main \
|
||||
--push-branch=image-updates \
|
||||
--author-name=flux \
|
||||
--author-email=flux@example.com \
|
||||
--commit-template="{{range .Updated.Images}}{{println .}}{{end}}"
|
||||
`,
|
||||
RunE: createImageUpdateRun,
|
||||
}
|
||||
|
||||
type imageUpdateFlags struct {
|
||||
// git checkout spec
|
||||
gitRepoRef string
|
||||
branch string
|
||||
// commit spec
|
||||
gitRepoRef string
|
||||
gitRepoPath string
|
||||
checkoutBranch string
|
||||
pushBranch string
|
||||
commitTemplate string
|
||||
authorName string
|
||||
authorEmail string
|
||||
@@ -50,8 +69,10 @@ var imageUpdateArgs = imageUpdateFlags{}
|
||||
|
||||
func init() {
|
||||
flags := createImageUpdateCmd.Flags()
|
||||
flags.StringVar(&imageUpdateArgs.gitRepoRef, "git-repo-ref", "", "the name of a GitRepository resource with details of the upstream git repository")
|
||||
flags.StringVar(&imageUpdateArgs.branch, "branch", "", "the branch to checkout and push commits to")
|
||||
flags.StringVar(&imageUpdateArgs.gitRepoRef, "git-repo-ref", "", "the name of a GitRepository resource with details of the upstream Git repository")
|
||||
flags.StringVar(&imageUpdateArgs.gitRepoPath, "git-repo-path", "", "path to the directory containing the manifests to be updated, defaults to the repository root")
|
||||
flags.StringVar(&imageUpdateArgs.checkoutBranch, "checkout-branch", "", "the branch to checkout")
|
||||
flags.StringVar(&imageUpdateArgs.pushBranch, "push-branch", "", "the branch to push commits to, defaults to the checkout branch if not specified")
|
||||
flags.StringVar(&imageUpdateArgs.commitTemplate, "commit-template", "", "a template for commit messages")
|
||||
flags.StringVar(&imageUpdateArgs.authorName, "author-name", "", "the name to use for commit author")
|
||||
flags.StringVar(&imageUpdateArgs.authorEmail, "author-email", "", "the email to use for commit author")
|
||||
@@ -69,8 +90,16 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("a reference to a GitRepository is required (--git-repo-ref)")
|
||||
}
|
||||
|
||||
if imageUpdateArgs.branch == "" {
|
||||
return fmt.Errorf("the Git repository branch is required (--branch)")
|
||||
if imageUpdateArgs.checkoutBranch == "" {
|
||||
return fmt.Errorf("the Git repository branch is required (--checkout-branch)")
|
||||
}
|
||||
|
||||
if imageUpdateArgs.authorName == "" {
|
||||
return fmt.Errorf("the author name is required (--author-name)")
|
||||
}
|
||||
|
||||
if imageUpdateArgs.authorEmail == "" {
|
||||
return fmt.Errorf("the author email is required (--author-email)")
|
||||
}
|
||||
|
||||
labels, err := parseLabels()
|
||||
@@ -89,9 +118,11 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
|
||||
GitRepositoryRef: meta.LocalObjectReference{
|
||||
Name: imageUpdateArgs.gitRepoRef,
|
||||
},
|
||||
Branch: imageUpdateArgs.branch,
|
||||
Branch: imageUpdateArgs.checkoutBranch,
|
||||
},
|
||||
Interval: metav1.Duration{
|
||||
Duration: createArgs.interval,
|
||||
},
|
||||
Interval: metav1.Duration{Duration: createArgs.interval},
|
||||
Commit: autov1.CommitSpec{
|
||||
AuthorName: imageUpdateArgs.authorName,
|
||||
AuthorEmail: imageUpdateArgs.authorEmail,
|
||||
@@ -100,6 +131,19 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
|
||||
},
|
||||
}
|
||||
|
||||
if imageUpdateArgs.pushBranch != "" {
|
||||
update.Spec.Push = &autov1.PushSpec{
|
||||
Branch: imageUpdateArgs.pushBranch,
|
||||
}
|
||||
}
|
||||
|
||||
if imageUpdateArgs.gitRepoPath != "" {
|
||||
update.Spec.Update = &autov1.UpdateStrategy{
|
||||
Path: imageUpdateArgs.gitRepoPath,
|
||||
Strategy: autov1.UpdateStrategySetters,
|
||||
}
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
return printExport(exportImageUpdate(&update))
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -142,7 +143,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
Interval: metav1.Duration{
|
||||
Duration: createArgs.interval,
|
||||
},
|
||||
Path: kustomizationArgs.path.String(),
|
||||
Path: filepath.ToSlash(kustomizationArgs.path.String()),
|
||||
Prune: kustomizationArgs.prune,
|
||||
SourceRef: kustomizev1.CrossNamespaceSourceReference{
|
||||
Kind: kustomizationArgs.source.Kind,
|
||||
|
||||
@@ -18,15 +18,12 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var createSecretCmd = &cobra.Command{
|
||||
@@ -39,23 +36,6 @@ func init() {
|
||||
createCmd.AddCommand(createSecretCmd)
|
||||
}
|
||||
|
||||
func makeSecret(name string) (corev1.Secret, error) {
|
||||
secretLabels, err := parseLabels()
|
||||
if err != nil {
|
||||
return corev1.Secret{}, err
|
||||
}
|
||||
|
||||
return corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: rootArgs.namespace,
|
||||
Labels: secretLabels,
|
||||
},
|
||||
StringData: map[string]string{},
|
||||
Data: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.Secret) error {
|
||||
namespacedName := types.NamespacedName{
|
||||
Namespace: secret.GetNamespace(),
|
||||
@@ -81,19 +61,3 @@ func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.S
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func exportSecret(secret corev1.Secret) error {
|
||||
secret.TypeMeta = metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
}
|
||||
|
||||
data, err := yaml.Marshal(secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("---")
|
||||
fmt.Println(resourceToString(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,13 +21,14 @@ import (
|
||||
"crypto/elliptic"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/pkg/ssh"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||
)
|
||||
|
||||
var createSecretGitCmd = &cobra.Command{
|
||||
@@ -76,6 +77,7 @@ type secretGitFlags struct {
|
||||
keyAlgorithm flags.PublicKeyAlgorithm
|
||||
rsaBits flags.RSAKeyBits
|
||||
ecdsaCurve flags.ECDSACurve
|
||||
caFile string
|
||||
}
|
||||
|
||||
var secretGitArgs = NewSecretGitFlags()
|
||||
@@ -87,13 +89,14 @@ func init() {
|
||||
createSecretGitCmd.Flags().Var(&secretGitArgs.keyAlgorithm, "ssh-key-algorithm", secretGitArgs.keyAlgorithm.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().StringVar(&secretGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
|
||||
|
||||
createSecretCmd.AddCommand(createSecretGitCmd)
|
||||
}
|
||||
|
||||
func NewSecretGitFlags() secretGitFlags {
|
||||
return secretGitFlags{
|
||||
keyAlgorithm: "rsa",
|
||||
keyAlgorithm: flags.PublicKeyAlgorithm(sourcesecret.RSAPrivateKeyAlgorithm),
|
||||
rsaBits: 2048,
|
||||
ecdsaCurve: flags.ECDSACurve{Curve: elliptic.P384()},
|
||||
}
|
||||
@@ -104,11 +107,6 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("secret name is required")
|
||||
}
|
||||
name := args[0]
|
||||
secret, err := makeSecret(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if secretGitArgs.url == "" {
|
||||
return fmt.Errorf("url is required")
|
||||
}
|
||||
@@ -118,88 +116,63 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("git URL parse failed: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
switch u.Scheme {
|
||||
case "ssh":
|
||||
pair, err := generateKeyPair(ctx, secretGitArgs.keyAlgorithm, secretGitArgs.rsaBits, secretGitArgs.ecdsaCurve)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hostKey, err := scanHostKey(ctx, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret.StringData = map[string]string{
|
||||
"identity": string(pair.PrivateKey),
|
||||
"identity.pub": string(pair.PublicKey),
|
||||
"known_hosts": string(hostKey),
|
||||
}
|
||||
|
||||
if !createArgs.export {
|
||||
logger.Generatef("deploy key: %s", string(pair.PublicKey))
|
||||
}
|
||||
case "http", "https":
|
||||
if secretGitArgs.username == "" || secretGitArgs.password == "" {
|
||||
return fmt.Errorf("for Git over HTTP/S the username and password are required")
|
||||
}
|
||||
|
||||
// TODO: add cert data when it's implemented in source-controller
|
||||
secret.StringData = map[string]string{
|
||||
"username": secretGitArgs.username,
|
||||
"password": secretGitArgs.password,
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
return exportSecret(secret)
|
||||
}
|
||||
|
||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
labels, err := parseLabels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
||||
opts := sourcesecret.Options{
|
||||
Name: name,
|
||||
Namespace: rootArgs.namespace,
|
||||
Labels: labels,
|
||||
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "ssh":
|
||||
opts.SSHHostname = u.Host
|
||||
opts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(secretGitArgs.keyAlgorithm)
|
||||
opts.RSAKeyBits = int(secretGitArgs.rsaBits)
|
||||
opts.ECDSACurve = secretGitArgs.ecdsaCurve.Curve
|
||||
case "http", "https":
|
||||
if secretGitArgs.username == "" || secretGitArgs.password == "" {
|
||||
return fmt.Errorf("for Git over HTTP/S the username and password are required")
|
||||
}
|
||||
opts.Username = secretGitArgs.username
|
||||
opts.Password = secretGitArgs.password
|
||||
opts.CAFilePath = secretGitArgs.caFile
|
||||
default:
|
||||
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
||||
}
|
||||
|
||||
secret, err := sourcesecret.Generate(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
fmt.Println(secret.Content)
|
||||
return nil
|
||||
}
|
||||
|
||||
var s corev1.Secret
|
||||
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
|
||||
logger.Generatef("deploy key: %s", ppk)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateKeyPair(ctx context.Context, alg flags.PublicKeyAlgorithm, rsa flags.RSAKeyBits, ecdsa flags.ECDSACurve) (*ssh.KeyPair, error) {
|
||||
var keyGen ssh.KeyPairGenerator
|
||||
switch algorithm := alg.String(); algorithm {
|
||||
case "rsa":
|
||||
keyGen = ssh.NewRSAGenerator(int(rsa))
|
||||
case "ecdsa":
|
||||
keyGen = ssh.NewECDSAGenerator(ecdsa.Curve)
|
||||
case "ed25519":
|
||||
keyGen = ssh.NewEd25519Generator()
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported public key algorithm: %s", algorithm)
|
||||
}
|
||||
pair, err := keyGen.Generate()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("key pair generation failed, error: %w", err)
|
||||
}
|
||||
return pair, nil
|
||||
}
|
||||
|
||||
func scanHostKey(ctx context.Context, url *url.URL) ([]byte, error) {
|
||||
host := url.Host
|
||||
if url.Port() == "" {
|
||||
host = host + ":22"
|
||||
}
|
||||
hostKey, err := ssh.ScanHostKey(host, 30*time.Second)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SSH key scan for host %s failed, error: %w", host, err)
|
||||
}
|
||||
return hostKey, nil
|
||||
}
|
||||
|
||||
@@ -21,8 +21,11 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||
)
|
||||
|
||||
var createSecretHelmCmd = &cobra.Command{
|
||||
@@ -30,8 +33,8 @@ var createSecretHelmCmd = &cobra.Command{
|
||||
Short: "Create or update a Kubernetes secret for Helm repository authentication",
|
||||
Long: `
|
||||
The create secret helm command generates a Kubernetes secret with basic authentication credentials.`,
|
||||
Example: ` # Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS
|
||||
|
||||
Example: `
|
||||
# Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS
|
||||
flux create secret helm repo-auth \
|
||||
--namespace=my-namespace \
|
||||
--username=my-username \
|
||||
@@ -72,36 +75,45 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("secret name is required")
|
||||
}
|
||||
name := args[0]
|
||||
secret, err := makeSecret(name)
|
||||
|
||||
labels, err := parseLabels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if secretHelmArgs.username != "" && secretHelmArgs.password != "" {
|
||||
secret.StringData["username"] = secretHelmArgs.username
|
||||
secret.StringData["password"] = secretHelmArgs.password
|
||||
opts := sourcesecret.Options{
|
||||
Name: name,
|
||||
Namespace: rootArgs.namespace,
|
||||
Labels: labels,
|
||||
Username: secretHelmArgs.username,
|
||||
Password: secretHelmArgs.password,
|
||||
CAFilePath: secretHelmArgs.caFile,
|
||||
CertFilePath: secretHelmArgs.certFile,
|
||||
KeyFilePath: secretHelmArgs.keyFile,
|
||||
}
|
||||
|
||||
if err = populateSecretTLS(&secret, secretHelmArgs.secretTLSFlags); err != nil {
|
||||
secret, err := sourcesecret.Generate(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
return exportSecret(secret)
|
||||
fmt.Println(secret.Content)
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
||||
var s corev1.Secret
|
||||
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,13 +19,14 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||
)
|
||||
|
||||
var createSecretTLSCmd = &cobra.Command{
|
||||
@@ -68,61 +69,48 @@ func init() {
|
||||
createSecretCmd.AddCommand(createSecretTLSCmd)
|
||||
}
|
||||
|
||||
func populateSecretTLS(secret *corev1.Secret, args secretTLSFlags) error {
|
||||
if args.certFile != "" && args.keyFile != "" {
|
||||
cert, err := ioutil.ReadFile(args.certFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read repository cert file '%s': %w", args.certFile, err)
|
||||
}
|
||||
secret.StringData["certFile"] = string(cert)
|
||||
|
||||
key, err := ioutil.ReadFile(args.keyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read repository key file '%s': %w", args.keyFile, err)
|
||||
}
|
||||
secret.StringData["keyFile"] = string(key)
|
||||
}
|
||||
|
||||
if args.caFile != "" {
|
||||
ca, err := ioutil.ReadFile(args.caFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read repository CA file '%s': %w", args.caFile, err)
|
||||
}
|
||||
secret.StringData["caFile"] = string(ca)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("secret name is required")
|
||||
}
|
||||
name := args[0]
|
||||
secret, err := makeSecret(name)
|
||||
|
||||
labels, err := parseLabels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = populateSecretTLS(&secret, secretTLSArgs); err != nil {
|
||||
opts := sourcesecret.Options{
|
||||
Name: name,
|
||||
Namespace: rootArgs.namespace,
|
||||
Labels: labels,
|
||||
CAFilePath: secretTLSArgs.caFile,
|
||||
CertFilePath: secretTLSArgs.certFile,
|
||||
KeyFilePath: secretTLSArgs.keyFile,
|
||||
}
|
||||
secret, err := sourcesecret.Generate(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
return exportSecret(secret)
|
||||
fmt.Println(secret.Content)
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
||||
var s corev1.Secret
|
||||
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -33,27 +35,26 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||
)
|
||||
|
||||
type SourceGitFlags struct {
|
||||
GitURL string
|
||||
GitBranch string
|
||||
GitTag string
|
||||
GitSemver string
|
||||
GitUsername string
|
||||
GitPassword string
|
||||
|
||||
GitKeyAlgorithm flags.PublicKeyAlgorithm
|
||||
GitRSABits flags.RSAKeyBits
|
||||
GitECDSACurve flags.ECDSACurve
|
||||
GitSecretRef string
|
||||
GitImplementation flags.GitImplementation
|
||||
type sourceGitFlags struct {
|
||||
url string
|
||||
branch string
|
||||
tag string
|
||||
semver string
|
||||
username string
|
||||
password string
|
||||
caFile string
|
||||
keyAlgorithm flags.PublicKeyAlgorithm
|
||||
keyRSABits flags.RSAKeyBits
|
||||
keyECDSACurve flags.ECDSACurve
|
||||
secretRef string
|
||||
gitImplementation flags.GitImplementation
|
||||
}
|
||||
|
||||
var createSourceGitCmd = &cobra.Command{
|
||||
@@ -100,29 +101,30 @@ For private Git repositories, the basic authentication credentials are stored in
|
||||
RunE: createSourceGitCmdRun,
|
||||
}
|
||||
|
||||
var sourceArgs = NewSourceGitFlags()
|
||||
var sourceGitArgs = newSourceGitFlags()
|
||||
|
||||
func init() {
|
||||
createSourceGitCmd.Flags().StringVar(&sourceArgs.GitURL, "url", "", "git address, e.g. ssh://git@host/org/repository")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceArgs.GitBranch, "branch", "master", "git branch")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceArgs.GitTag, "tag", "", "git tag")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceArgs.GitSemver, "tag-semver", "", "git tag semver range")
|
||||
createSourceGitCmd.Flags().StringVarP(&sourceArgs.GitUsername, "username", "u", "", "basic authentication username")
|
||||
createSourceGitCmd.Flags().StringVarP(&sourceArgs.GitPassword, "password", "p", "", "basic authentication password")
|
||||
createSourceGitCmd.Flags().Var(&sourceArgs.GitKeyAlgorithm, "ssh-key-algorithm", sourceArgs.GitKeyAlgorithm.Description())
|
||||
createSourceGitCmd.Flags().Var(&sourceArgs.GitRSABits, "ssh-rsa-bits", sourceArgs.GitRSABits.Description())
|
||||
createSourceGitCmd.Flags().Var(&sourceArgs.GitECDSACurve, "ssh-ecdsa-curve", sourceArgs.GitECDSACurve.Description())
|
||||
createSourceGitCmd.Flags().StringVarP(&sourceArgs.GitSecretRef, "secret-ref", "", "", "the name of an existing secret containing SSH or basic credentials")
|
||||
createSourceGitCmd.Flags().Var(&sourceArgs.GitImplementation, "git-implementation", sourceArgs.GitImplementation.Description())
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.url, "url", "", "git address, e.g. ssh://git@host/org/repository")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.branch, "branch", "master", "git branch")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.tag, "tag", "", "git tag")
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.semver, "tag-semver", "", "git tag semver range")
|
||||
createSourceGitCmd.Flags().StringVarP(&sourceGitArgs.username, "username", "u", "", "basic authentication username")
|
||||
createSourceGitCmd.Flags().StringVarP(&sourceGitArgs.password, "password", "p", "", "basic authentication password")
|
||||
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyAlgorithm, "ssh-key-algorithm", sourceGitArgs.keyAlgorithm.Description())
|
||||
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyRSABits, "ssh-rsa-bits", sourceGitArgs.keyRSABits.Description())
|
||||
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyECDSACurve, "ssh-ecdsa-curve", sourceGitArgs.keyECDSACurve.Description())
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.secretRef, "secret-ref", "", "the name of an existing secret containing SSH or basic credentials")
|
||||
createSourceGitCmd.Flags().Var(&sourceGitArgs.gitImplementation, "git-implementation", sourceGitArgs.gitImplementation.Description())
|
||||
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates, requires libgit2")
|
||||
|
||||
createSourceCmd.AddCommand(createSourceGitCmd)
|
||||
}
|
||||
|
||||
func NewSourceGitFlags() SourceGitFlags {
|
||||
return SourceGitFlags{
|
||||
GitKeyAlgorithm: "rsa",
|
||||
GitRSABits: 2048,
|
||||
GitECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()},
|
||||
func newSourceGitFlags() sourceGitFlags {
|
||||
return sourceGitFlags{
|
||||
keyAlgorithm: flags.PublicKeyAlgorithm(sourcesecret.RSAPrivateKeyAlgorithm),
|
||||
keyRSABits: 2048,
|
||||
keyECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,20 +134,27 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
name := args[0]
|
||||
|
||||
if sourceArgs.GitURL == "" {
|
||||
if sourceGitArgs.url == "" {
|
||||
return fmt.Errorf("url is required")
|
||||
}
|
||||
|
||||
if sourceGitArgs.gitImplementation.String() != sourcev1.LibGit2Implementation && sourceGitArgs.caFile != "" {
|
||||
return fmt.Errorf("specifing a CA file requires --git-implementation=%s", sourcev1.LibGit2Implementation)
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
u, err := url.Parse(sourceArgs.GitURL)
|
||||
u, err := url.Parse(sourceGitArgs.url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git URL parse failed: %w", err)
|
||||
}
|
||||
if u.Scheme != "ssh" && u.Scheme != "http" && u.Scheme != "https" {
|
||||
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
||||
}
|
||||
|
||||
sourceLabels, err := parseLabels()
|
||||
if err != nil {
|
||||
@@ -159,7 +168,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
Labels: sourceLabels,
|
||||
},
|
||||
Spec: sourcev1.GitRepositorySpec{
|
||||
URL: sourceArgs.GitURL,
|
||||
URL: sourceGitArgs.url,
|
||||
Interval: metav1.Duration{
|
||||
Duration: createArgs.interval,
|
||||
},
|
||||
@@ -167,24 +176,25 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
},
|
||||
}
|
||||
|
||||
if sourceArgs.GitImplementation != "" {
|
||||
gitRepository.Spec.GitImplementation = sourceArgs.GitImplementation.String()
|
||||
if sourceGitArgs.gitImplementation != "" {
|
||||
gitRepository.Spec.GitImplementation = sourceGitArgs.gitImplementation.String()
|
||||
}
|
||||
|
||||
if sourceArgs.GitSemver != "" {
|
||||
gitRepository.Spec.Reference.SemVer = sourceArgs.GitSemver
|
||||
} else if sourceArgs.GitTag != "" {
|
||||
gitRepository.Spec.Reference.Tag = sourceArgs.GitTag
|
||||
if sourceGitArgs.semver != "" {
|
||||
gitRepository.Spec.Reference.SemVer = sourceGitArgs.semver
|
||||
} else if sourceGitArgs.tag != "" {
|
||||
gitRepository.Spec.Reference.Tag = sourceGitArgs.tag
|
||||
} else {
|
||||
gitRepository.Spec.Reference.Branch = sourceArgs.GitBranch
|
||||
gitRepository.Spec.Reference.Branch = sourceGitArgs.branch
|
||||
}
|
||||
|
||||
if sourceGitArgs.secretRef != "" {
|
||||
gitRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: sourceGitArgs.secretRef,
|
||||
}
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
if sourceArgs.GitSecretRef != "" {
|
||||
gitRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: sourceArgs.GitSecretRef,
|
||||
}
|
||||
}
|
||||
return exportGit(gitRepository)
|
||||
}
|
||||
|
||||
@@ -196,82 +206,54 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
withAuth := false
|
||||
// TODO(hidde): move all auth prep to separate func?
|
||||
if sourceArgs.GitSecretRef != "" {
|
||||
withAuth = true
|
||||
} else if u.Scheme == "ssh" {
|
||||
logger.Generatef("generating deploy key pair")
|
||||
pair, err := generateKeyPair(ctx, sourceArgs.GitKeyAlgorithm, sourceArgs.GitRSABits, sourceArgs.GitECDSACurve)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Successf("deploy key: %s", pair.PublicKey)
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Have you added the deploy key to your repository",
|
||||
IsConfirm: true,
|
||||
}
|
||||
if _, err := prompt.Run(); err != nil {
|
||||
return fmt.Errorf("aborting")
|
||||
}
|
||||
|
||||
logger.Actionf("collecting preferred public key from SSH server")
|
||||
hostKey, err := scanHostKey(ctx, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Successf("collected public key from SSH server:\n%s", hostKey)
|
||||
|
||||
logger.Actionf("applying secret with keys")
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: rootArgs.namespace,
|
||||
Labels: sourceLabels,
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"identity": string(pair.PrivateKey),
|
||||
"identity.pub": string(pair.PublicKey),
|
||||
"known_hosts": string(hostKey),
|
||||
},
|
||||
}
|
||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
withAuth = true
|
||||
} else if sourceArgs.GitUsername != "" && sourceArgs.GitPassword != "" {
|
||||
logger.Actionf("applying secret with basic auth credentials")
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: rootArgs.namespace,
|
||||
Labels: sourceLabels,
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"username": sourceArgs.GitUsername,
|
||||
"password": sourceArgs.GitPassword,
|
||||
},
|
||||
}
|
||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
withAuth = true
|
||||
}
|
||||
|
||||
if withAuth {
|
||||
logger.Successf("authentication configured")
|
||||
}
|
||||
|
||||
logger.Generatef("generating GitRepository source")
|
||||
|
||||
if withAuth {
|
||||
secretName := name
|
||||
if sourceArgs.GitSecretRef != "" {
|
||||
secretName = sourceArgs.GitSecretRef
|
||||
if sourceGitArgs.secretRef == "" {
|
||||
secretOpts := sourcesecret.Options{
|
||||
Name: name,
|
||||
Namespace: rootArgs.namespace,
|
||||
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||
}
|
||||
gitRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: secretName,
|
||||
switch u.Scheme {
|
||||
case "ssh":
|
||||
secretOpts.SSHHostname = u.Host
|
||||
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(sourceGitArgs.keyAlgorithm)
|
||||
secretOpts.RSAKeyBits = int(sourceGitArgs.keyRSABits)
|
||||
secretOpts.ECDSACurve = sourceGitArgs.keyECDSACurve.Curve
|
||||
case "https":
|
||||
secretOpts.Username = sourceGitArgs.username
|
||||
secretOpts.Password = sourceGitArgs.password
|
||||
secretOpts.CAFilePath = sourceGitArgs.caFile
|
||||
}
|
||||
secret, err := sourcesecret.Generate(secretOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var s corev1.Secret
|
||||
if err = yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s.StringData) > 0 {
|
||||
if hk, ok := s.StringData[sourcesecret.KnownHostsSecretKey]; ok {
|
||||
logger.Successf("collected public key from SSH server:\n%s", hk)
|
||||
}
|
||||
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
|
||||
logger.Generatef("deploy key: %s", ppk)
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Have you added the deploy key to your repository",
|
||||
IsConfirm: true,
|
||||
}
|
||||
if _, err := prompt.Run(); err != nil {
|
||||
return fmt.Errorf("aborting")
|
||||
}
|
||||
}
|
||||
logger.Actionf("applying secret with repository credentials")
|
||||
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||
return err
|
||||
}
|
||||
gitRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: s.Name,
|
||||
}
|
||||
logger.Successf("authentication configured")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,12 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||
)
|
||||
|
||||
var createSourceHelmCmd = &cobra.Command{
|
||||
@@ -149,46 +151,27 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
||||
logger.Generatef("generating HelmRepository source")
|
||||
if sourceHelmArgs.secretRef == "" {
|
||||
secretName := fmt.Sprintf("helm-%s", name)
|
||||
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Namespace: rootArgs.namespace,
|
||||
Labels: sourceLabels,
|
||||
},
|
||||
StringData: map[string]string{},
|
||||
secretOpts := sourcesecret.Options{
|
||||
Name: secretName,
|
||||
Namespace: rootArgs.namespace,
|
||||
Username: sourceHelmArgs.username,
|
||||
Password: sourceHelmArgs.password,
|
||||
CertFilePath: sourceHelmArgs.certFile,
|
||||
KeyFilePath: sourceHelmArgs.keyFile,
|
||||
CAFilePath: sourceHelmArgs.caFile,
|
||||
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||
}
|
||||
|
||||
if sourceHelmArgs.username != "" && sourceHelmArgs.password != "" {
|
||||
secret.StringData["username"] = sourceHelmArgs.username
|
||||
secret.StringData["password"] = sourceHelmArgs.password
|
||||
secret, err := sourcesecret.Generate(secretOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sourceHelmArgs.certFile != "" && sourceHelmArgs.keyFile != "" {
|
||||
cert, err := ioutil.ReadFile(sourceHelmArgs.certFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read repository cert file '%s': %w", sourceHelmArgs.certFile, err)
|
||||
}
|
||||
secret.StringData["certFile"] = string(cert)
|
||||
|
||||
key, err := ioutil.ReadFile(sourceHelmArgs.keyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read repository key file '%s': %w", sourceHelmArgs.keyFile, err)
|
||||
}
|
||||
secret.StringData["keyFile"] = string(key)
|
||||
var s corev1.Secret
|
||||
if err = yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sourceHelmArgs.caFile != "" {
|
||||
ca, err := ioutil.ReadFile(sourceHelmArgs.caFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read repository CA file '%s': %w", sourceHelmArgs.caFile, err)
|
||||
}
|
||||
secret.StringData["caFile"] = string(ca)
|
||||
}
|
||||
|
||||
if len(secret.StringData) > 0 {
|
||||
if len(s.StringData) > 0 {
|
||||
logger.Actionf("applying secret with repository credentials")
|
||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
||||
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||
return err
|
||||
}
|
||||
helmRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
var exportSourceBucketCmd = &cobra.Command{
|
||||
Use: "bucket [name]",
|
||||
Short: "Export Bucket sources in YAML format",
|
||||
Long: "The export source git command exports on or all Bucket sources in YAML format.",
|
||||
Long: "The export source git command exports one or all Bucket sources in YAML format.",
|
||||
Example: ` # Export all Bucket sources
|
||||
flux export source bucket --all > sources.yaml
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
var exportSourceGitCmd = &cobra.Command{
|
||||
Use: "git [name]",
|
||||
Short: "Export GitRepository sources in YAML format",
|
||||
Long: "The export source git command exports on or all GitRepository sources in YAML format.",
|
||||
Long: "The export source git command exports one or all GitRepository sources in YAML format.",
|
||||
Example: ` # Export all GitRepository sources
|
||||
flux export source git --all > sources.yaml
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
var exportSourceHelmCmd = &cobra.Command{
|
||||
Use: "helm [name]",
|
||||
Short: "Export HelmRepository sources in YAML format",
|
||||
Long: "The export source git command exports on or all HelmRepository sources in YAML format.",
|
||||
Long: "The export source git command exports one or all HelmRepository sources in YAML format.",
|
||||
Example: ` # Export all HelmRepository sources
|
||||
flux export source helm --all > sources.yaml
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
@@ -32,8 +34,8 @@ import (
|
||||
|
||||
var getCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get sources and resources",
|
||||
Long: "The get sub-commands print the statuses of sources and resources.",
|
||||
Short: "Get the resources and their status",
|
||||
Long: "The get sub-commands print the statuses of Flux resources.",
|
||||
}
|
||||
|
||||
type GetFlags struct {
|
||||
@@ -50,7 +52,7 @@ func init() {
|
||||
|
||||
type summarisable interface {
|
||||
listAdapter
|
||||
summariseItem(i int, includeNamespace bool) []string
|
||||
summariseItem(i int, includeNamespace bool, includeKind bool) []string
|
||||
headers(includeNamespace bool) []string
|
||||
}
|
||||
|
||||
@@ -63,11 +65,17 @@ func statusAndMessage(conditions []metav1.Condition) (string, string) {
|
||||
return string(metav1.ConditionFalse), "waiting to be reconciled"
|
||||
}
|
||||
|
||||
func nameColumns(item named, includeNamespace bool) []string {
|
||||
if includeNamespace {
|
||||
return []string{item.GetNamespace(), item.GetName()}
|
||||
func nameColumns(item named, includeNamespace bool, includeKind bool) []string {
|
||||
name := item.GetName()
|
||||
if includeKind {
|
||||
name = fmt.Sprintf("%s/%s",
|
||||
strings.ToLower(item.GetObjectKind().GroupVersionKind().Kind),
|
||||
item.GetName())
|
||||
}
|
||||
return []string{item.GetName()}
|
||||
if includeNamespace {
|
||||
return []string{item.GetNamespace(), name}
|
||||
}
|
||||
return []string{name}
|
||||
}
|
||||
|
||||
var namespaceHeader = []string{"Namespace"}
|
||||
@@ -100,17 +108,25 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
getAll := cmd.Use == "all"
|
||||
|
||||
if get.list.len() == 0 {
|
||||
logger.Failuref("no %s objects found in %s namespace", get.kind, rootArgs.namespace)
|
||||
if !getAll {
|
||||
logger.Failuref("no %s objects found in %s namespace", get.kind, rootArgs.namespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
header := get.list.headers(getArgs.allNamespaces)
|
||||
var rows [][]string
|
||||
for i := 0; i < get.list.len(); i++ {
|
||||
row := get.list.summariseItem(i, getArgs.allNamespaces)
|
||||
row := get.list.summariseItem(i, getArgs.allNamespaces, getAll)
|
||||
rows = append(rows, row)
|
||||
}
|
||||
utils.PrintTable(os.Stdout, header, rows)
|
||||
|
||||
if getAll {
|
||||
fmt.Println()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,11 +42,11 @@ func init() {
|
||||
getCmd.AddCommand(getHelmReleaseCmd)
|
||||
}
|
||||
|
||||
func (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool) []string {
|
||||
func (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||
item := a.Items[i]
|
||||
revision := item.Status.LastAppliedRevision
|
||||
status, msg := statusAndMessage(item.Status.Conditions)
|
||||
return append(nameColumns(&item, includeNamespace),
|
||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
|
||||
}
|
||||
|
||||
|
||||
66
cmd/flux/get_image_all.go
Normal file
66
cmd/flux/get_image_all.go
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 (
|
||||
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var getImageAllCmd = &cobra.Command{
|
||||
Use: "all",
|
||||
Short: "Get all image statuses",
|
||||
Long: "The get image sub-commands print the statuses of all image objects.",
|
||||
Example: ` # List all image objects in a namespace
|
||||
flux get images all --namespace=flux-system
|
||||
|
||||
# List all image objects in all namespaces
|
||||
flux get images all --all-namespaces
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c := getCommand{
|
||||
apiType: imageRepositoryType,
|
||||
list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
|
||||
}
|
||||
if err := c.run(cmd, args); err != nil {
|
||||
logger.Failuref(err.Error())
|
||||
}
|
||||
|
||||
c = getCommand{
|
||||
apiType: imagePolicyType,
|
||||
list: &imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
|
||||
}
|
||||
if err := c.run(cmd, args); err != nil {
|
||||
logger.Failuref(err.Error())
|
||||
}
|
||||
|
||||
c = getCommand{
|
||||
apiType: imageUpdateAutomationType,
|
||||
list: &imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
|
||||
}
|
||||
if err := c.run(cmd, args); err != nil {
|
||||
logger.Failuref(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
getImageCmd.AddCommand(getImageAllCmd)
|
||||
}
|
||||
@@ -42,10 +42,10 @@ func init() {
|
||||
getImageCmd.AddCommand(getImagePolicyCmd)
|
||||
}
|
||||
|
||||
func (s imagePolicyListAdapter) summariseItem(i int, includeNamespace bool) []string {
|
||||
func (s imagePolicyListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||
item := s.Items[i]
|
||||
status, msg := statusAndMessage(item.Status.Conditions)
|
||||
return append(nameColumns(&item, includeNamespace), status, msg, item.Status.LatestImage)
|
||||
return append(nameColumns(&item, includeNamespace, includeKind), status, msg, item.Status.LatestImage)
|
||||
}
|
||||
|
||||
func (s imagePolicyListAdapter) headers(includeNamespace bool) []string {
|
||||
|
||||
@@ -46,14 +46,14 @@ func init() {
|
||||
getImageCmd.AddCommand(getImageRepositoryCmd)
|
||||
}
|
||||
|
||||
func (s imageRepositoryListAdapter) summariseItem(i int, includeNamespace bool) []string {
|
||||
func (s imageRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||
item := s.Items[i]
|
||||
status, msg := statusAndMessage(item.Status.Conditions)
|
||||
var lastScan string
|
||||
if item.Status.LastScanResult != nil {
|
||||
lastScan = item.Status.LastScanResult.ScanTime.Time.Format(time.RFC3339)
|
||||
}
|
||||
return append(nameColumns(&item, includeNamespace),
|
||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||
status, msg, lastScan, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
|
||||
}
|
||||
|
||||
|
||||
@@ -46,14 +46,14 @@ func init() {
|
||||
getImageCmd.AddCommand(getImageUpdateCmd)
|
||||
}
|
||||
|
||||
func (s imageUpdateAutomationListAdapter) summariseItem(i int, includeNamespace bool) []string {
|
||||
func (s imageUpdateAutomationListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||
item := s.Items[i]
|
||||
status, msg := statusAndMessage(item.Status.Conditions)
|
||||
var lastRun string
|
||||
if item.Status.LastAutomationRunTime != nil {
|
||||
lastRun = item.Status.LastAutomationRunTime.Time.Format(time.RFC3339)
|
||||
}
|
||||
return append(nameColumns(&item, includeNamespace), status, msg, lastRun, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
|
||||
return append(nameColumns(&item, includeNamespace, includeKind), status, msg, lastRun, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
|
||||
}
|
||||
|
||||
func (s imageUpdateAutomationListAdapter) headers(includeNamespace bool) []string {
|
||||
@@ -42,11 +42,11 @@ func init() {
|
||||
getCmd.AddCommand(getKsCmd)
|
||||
}
|
||||
|
||||
func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool) []string {
|
||||
func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||
item := a.Items[i]
|
||||
revision := item.Status.LastAppliedRevision
|
||||
status, msg := statusAndMessage(item.Status.Conditions)
|
||||
return append(nameColumns(&item, includeNamespace),
|
||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ func getReceiverCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
var rows [][]string
|
||||
for _, receiver := range list.Items {
|
||||
row := []string{}
|
||||
var row []string
|
||||
if c := apimeta.FindStatusCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
|
||||
row = []string{
|
||||
receiver.GetName(),
|
||||
|
||||
73
cmd/flux/get_source_all.go
Normal file
73
cmd/flux/get_source_all.go
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
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 (
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var getSourceAllCmd = &cobra.Command{
|
||||
Use: "all",
|
||||
Short: "Get all source statuses",
|
||||
Long: "The get sources all command print the statuses of all sources.",
|
||||
Example: ` # List all sources in a namespace
|
||||
flux get sources all --namespace=flux-system
|
||||
|
||||
# List all sources in all namespaces
|
||||
flux get sources all --all-namespaces
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c := getCommand{
|
||||
apiType: bucketType,
|
||||
list: &bucketListAdapter{&sourcev1.BucketList{}},
|
||||
}
|
||||
if err := c.run(cmd, args); err != nil {
|
||||
logger.Failuref(err.Error())
|
||||
}
|
||||
|
||||
c = getCommand{
|
||||
apiType: gitRepositoryType,
|
||||
list: &gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},
|
||||
}
|
||||
if err := c.run(cmd, args); err != nil {
|
||||
logger.Failuref(err.Error())
|
||||
}
|
||||
|
||||
c = getCommand{
|
||||
apiType: helmRepositoryType,
|
||||
list: &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},
|
||||
}
|
||||
if err := c.run(cmd, args); err != nil {
|
||||
logger.Failuref(err.Error())
|
||||
}
|
||||
|
||||
c = getCommand{
|
||||
apiType: helmChartType,
|
||||
list: &helmChartListAdapter{&sourcev1.HelmChartList{}},
|
||||
}
|
||||
if err := c.run(cmd, args); err != nil {
|
||||
logger.Failuref(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
getSourceCmd.AddCommand(getSourceAllCmd)
|
||||
}
|
||||
@@ -44,14 +44,14 @@ func init() {
|
||||
getSourceCmd.AddCommand(getSourceBucketCmd)
|
||||
}
|
||||
|
||||
func (a *bucketListAdapter) summariseItem(i int, includeNamespace bool) []string {
|
||||
func (a *bucketListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||
item := a.Items[i]
|
||||
var revision string
|
||||
if item.GetArtifact() != nil {
|
||||
revision = item.GetArtifact().Revision
|
||||
}
|
||||
status, msg := statusAndMessage(item.Status.Conditions)
|
||||
return append(nameColumns(&item, includeNamespace),
|
||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
|
||||
}
|
||||
|
||||
|
||||
@@ -44,14 +44,14 @@ func init() {
|
||||
getSourceCmd.AddCommand(getSourceHelmChartCmd)
|
||||
}
|
||||
|
||||
func (a *helmChartListAdapter) summariseItem(i int, includeNamespace bool) []string {
|
||||
func (a *helmChartListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||
item := a.Items[i]
|
||||
var revision string
|
||||
if item.GetArtifact() != nil {
|
||||
revision = item.GetArtifact().Revision
|
||||
}
|
||||
status, msg := statusAndMessage(item.Status.Conditions)
|
||||
return append(nameColumns(&item, includeNamespace),
|
||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
|
||||
}
|
||||
|
||||
|
||||
@@ -44,14 +44,14 @@ func init() {
|
||||
getSourceCmd.AddCommand(getSourceGitCmd)
|
||||
}
|
||||
|
||||
func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool) []string {
|
||||
func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||
item := a.Items[i]
|
||||
var revision string
|
||||
if item.GetArtifact() != nil {
|
||||
revision = item.GetArtifact().Revision
|
||||
}
|
||||
status, msg := statusAndMessage(item.Status.Conditions)
|
||||
return append(nameColumns(&item, includeNamespace),
|
||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
|
||||
}
|
||||
|
||||
|
||||
@@ -44,14 +44,14 @@ func init() {
|
||||
getSourceCmd.AddCommand(getSourceHelmCmd)
|
||||
}
|
||||
|
||||
func (a *helmRepositoryListAdapter) summariseItem(i int, includeNamespace bool) []string {
|
||||
func (a *helmRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||
item := a.Items[i]
|
||||
var revision string
|
||||
if item.GetArtifact() != nil {
|
||||
revision = item.GetArtifact().Revision
|
||||
}
|
||||
status, msg := statusAndMessage(item.Status.Conditions)
|
||||
return append(nameColumns(&item, includeNamespace),
|
||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
|
||||
}
|
||||
|
||||
|
||||
@@ -30,19 +30,23 @@ import (
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
"github.com/fluxcd/flux2/pkg/status"
|
||||
)
|
||||
|
||||
var installCmd = &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "Install the toolkit components",
|
||||
Long: `The install command deploys the toolkit components in the specified namespace.
|
||||
Short: "Install or upgrade Flux",
|
||||
Long: `The install command deploys Flux in the specified namespace.
|
||||
If a previous version is installed, then an in-place upgrade will be performed.`,
|
||||
Example: ` # Install the latest version in the flux-system namespace
|
||||
flux install --version=latest --namespace=flux-system
|
||||
|
||||
# Dry-run install for a specific version and a series of components
|
||||
# Install a specific version and a series of components
|
||||
flux install --dry-run --version=v0.0.7 --components="source-controller,kustomize-controller"
|
||||
|
||||
# Install Flux onto tainted Kubernetes nodes
|
||||
flux install --toleration-keys=node.kubernetes.io/dedicated-to-flux
|
||||
|
||||
# Dry-run install with manifests preview
|
||||
flux install --dry-run --verbose
|
||||
|
||||
@@ -52,91 +56,119 @@ If a previous version is installed, then an in-place upgrade will be performed.`
|
||||
RunE: installCmdRun,
|
||||
}
|
||||
|
||||
var (
|
||||
installExport bool
|
||||
installDryRun bool
|
||||
installManifestsPath string
|
||||
installVersion string
|
||||
installDefaultComponents []string
|
||||
installExtraComponents []string
|
||||
installRegistry string
|
||||
installImagePullSecret string
|
||||
installWatchAllNamespaces bool
|
||||
installNetworkPolicy bool
|
||||
installArch flags.Arch
|
||||
installLogLevel = flags.LogLevel(rootArgs.defaults.LogLevel)
|
||||
installClusterDomain string
|
||||
)
|
||||
type installFlags struct {
|
||||
export bool
|
||||
dryRun bool
|
||||
version string
|
||||
defaultComponents []string
|
||||
extraComponents []string
|
||||
registry string
|
||||
imagePullSecret string
|
||||
branch string
|
||||
watchAllNamespaces bool
|
||||
networkPolicy bool
|
||||
manifestsPath string
|
||||
arch flags.Arch
|
||||
logLevel flags.LogLevel
|
||||
tokenAuth bool
|
||||
clusterDomain string
|
||||
tolerationKeys []string
|
||||
}
|
||||
|
||||
var installArgs = NewInstallFlags()
|
||||
|
||||
func init() {
|
||||
installCmd.Flags().BoolVar(&installExport, "export", false,
|
||||
installCmd.Flags().BoolVar(&installArgs.export, "export", false,
|
||||
"write the install manifests to stdout and exit")
|
||||
installCmd.Flags().BoolVarP(&installDryRun, "dry-run", "", false,
|
||||
installCmd.Flags().BoolVarP(&installArgs.dryRun, "dry-run", "", false,
|
||||
"only print the object that would be applied")
|
||||
installCmd.Flags().StringVarP(&installVersion, "version", "v", rootArgs.defaults.Version,
|
||||
"toolkit version")
|
||||
installCmd.Flags().StringSliceVar(&installDefaultComponents, "components", rootArgs.defaults.Components,
|
||||
installCmd.Flags().StringVarP(&installArgs.version, "version", "v", "",
|
||||
"toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases")
|
||||
installCmd.Flags().StringSliceVar(&installArgs.defaultComponents, "components", rootArgs.defaults.Components,
|
||||
"list of components, accepts comma-separated values")
|
||||
installCmd.Flags().StringSliceVar(&installExtraComponents, "components-extra", nil,
|
||||
installCmd.Flags().StringSliceVar(&installArgs.extraComponents, "components-extra", nil,
|
||||
"list of components in addition to those supplied or defaulted, accepts comma-separated values")
|
||||
installCmd.Flags().StringVar(&installManifestsPath, "manifests", "", "path to the manifest directory")
|
||||
installCmd.Flags().StringVar(&installRegistry, "registry", rootArgs.defaults.Registry,
|
||||
installCmd.Flags().StringVar(&installArgs.manifestsPath, "manifests", "", "path to the manifest directory")
|
||||
installCmd.Flags().StringVar(&installArgs.registry, "registry", rootArgs.defaults.Registry,
|
||||
"container registry where the toolkit images are published")
|
||||
installCmd.Flags().StringVar(&installImagePullSecret, "image-pull-secret", "",
|
||||
installCmd.Flags().StringVar(&installArgs.imagePullSecret, "image-pull-secret", "",
|
||||
"Kubernetes secret name used for pulling the toolkit images from a private registry")
|
||||
installCmd.Flags().Var(&installArch, "arch", installArch.Description())
|
||||
installCmd.Flags().BoolVar(&installWatchAllNamespaces, "watch-all-namespaces", rootArgs.defaults.WatchAllNamespaces,
|
||||
installCmd.Flags().Var(&installArgs.arch, "arch", installArgs.arch.Description())
|
||||
installCmd.Flags().BoolVar(&installArgs.watchAllNamespaces, "watch-all-namespaces", rootArgs.defaults.WatchAllNamespaces,
|
||||
"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed")
|
||||
installCmd.Flags().Var(&installLogLevel, "log-level", installLogLevel.Description())
|
||||
installCmd.Flags().BoolVar(&installNetworkPolicy, "network-policy", rootArgs.defaults.NetworkPolicy,
|
||||
installCmd.Flags().Var(&installArgs.logLevel, "log-level", installArgs.logLevel.Description())
|
||||
installCmd.Flags().BoolVar(&installArgs.networkPolicy, "network-policy", rootArgs.defaults.NetworkPolicy,
|
||||
"deny ingress access to the toolkit controllers from other namespaces using network policies")
|
||||
installCmd.Flags().StringVar(&installClusterDomain, "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,
|
||||
"list of toleration keys used to schedule the components pods onto nodes with matching taints")
|
||||
installCmd.Flags().MarkHidden("manifests")
|
||||
installCmd.Flags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64")
|
||||
rootCmd.AddCommand(installCmd)
|
||||
}
|
||||
|
||||
func NewInstallFlags() installFlags {
|
||||
return installFlags{
|
||||
logLevel: flags.LogLevel(rootArgs.defaults.LogLevel),
|
||||
}
|
||||
}
|
||||
|
||||
func installCmdRun(cmd *cobra.Command, args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
components := append(installArgs.defaultComponents, installArgs.extraComponents...)
|
||||
err := utils.ValidateComponents(components)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ver, err := getVersion(installArgs.version); err != nil {
|
||||
return err
|
||||
} else {
|
||||
installArgs.version = ver
|
||||
}
|
||||
|
||||
if !installArgs.export {
|
||||
logger.Generatef("generating manifests")
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", rootArgs.namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
if !installExport {
|
||||
logger.Generatef("generating manifests")
|
||||
}
|
||||
|
||||
components := append(installDefaultComponents, installExtraComponents...)
|
||||
|
||||
if err := utils.ValidateComponents(components); err != nil {
|
||||
return err
|
||||
manifestsBase := ""
|
||||
if isEmbeddedVersion(installArgs.version) {
|
||||
if err := writeEmbeddedManifests(tmpDir); err != nil {
|
||||
return err
|
||||
}
|
||||
manifestsBase = tmpDir
|
||||
}
|
||||
|
||||
opts := install.Options{
|
||||
BaseURL: installManifestsPath,
|
||||
Version: installVersion,
|
||||
BaseURL: installArgs.manifestsPath,
|
||||
Version: installArgs.version,
|
||||
Namespace: rootArgs.namespace,
|
||||
Components: components,
|
||||
Registry: installRegistry,
|
||||
ImagePullSecret: installImagePullSecret,
|
||||
WatchAllNamespaces: installWatchAllNamespaces,
|
||||
NetworkPolicy: installNetworkPolicy,
|
||||
LogLevel: installLogLevel.String(),
|
||||
Registry: installArgs.registry,
|
||||
ImagePullSecret: installArgs.imagePullSecret,
|
||||
WatchAllNamespaces: installArgs.watchAllNamespaces,
|
||||
NetworkPolicy: installArgs.networkPolicy,
|
||||
LogLevel: installArgs.logLevel.String(),
|
||||
NotificationController: rootArgs.defaults.NotificationController,
|
||||
ManifestFile: fmt.Sprintf("%s.yaml", rootArgs.namespace),
|
||||
Timeout: rootArgs.timeout,
|
||||
ClusterDomain: installClusterDomain,
|
||||
ClusterDomain: installArgs.clusterDomain,
|
||||
TolerationKeys: installArgs.tolerationKeys,
|
||||
}
|
||||
|
||||
if installManifestsPath == "" {
|
||||
if installArgs.manifestsPath == "" {
|
||||
opts.BaseURL = install.MakeDefaultOptions().BaseURL
|
||||
}
|
||||
|
||||
manifest, err := install.Generate(opts)
|
||||
manifest, err := install.Generate(opts, manifestsBase)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install failed: %w", err)
|
||||
}
|
||||
@@ -147,9 +179,9 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if rootArgs.verbose {
|
||||
fmt.Print(manifest.Content)
|
||||
} else if installExport {
|
||||
} else if installArgs.export {
|
||||
fmt.Println("---")
|
||||
fmt.Println("# GitOps Toolkit revision", installVersion)
|
||||
fmt.Println("# Flux version:", installArgs.version)
|
||||
fmt.Println("# Components:", strings.Join(components, ","))
|
||||
fmt.Print(manifest.Content)
|
||||
fmt.Println("---")
|
||||
@@ -164,26 +196,33 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
kubectlArgs := []string{"apply", "-f", filepath.Join(tmpDir, manifest.Path)}
|
||||
if installDryRun {
|
||||
if installArgs.dryRun {
|
||||
kubectlArgs = append(kubectlArgs, "--dry-run=client")
|
||||
applyOutput = utils.ModeOS
|
||||
}
|
||||
if _, err := utils.ExecKubectlCommand(ctx, applyOutput, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
|
||||
return fmt.Errorf("install failed")
|
||||
return fmt.Errorf("install failed: %w", err)
|
||||
}
|
||||
|
||||
if installDryRun {
|
||||
if installArgs.dryRun {
|
||||
logger.Successf("install dry-run finished")
|
||||
return nil
|
||||
}
|
||||
|
||||
statusChecker, err := NewStatusChecker(time.Second, time.Minute)
|
||||
kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install failed: %w", err)
|
||||
}
|
||||
statusChecker, err := status.NewStatusChecker(kubeConfig, time.Second, rootArgs.timeout, logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install failed: %w", err)
|
||||
}
|
||||
componentRefs, err := buildComponentObjectRefs(components...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install failed: %w", err)
|
||||
}
|
||||
|
||||
logger.Waitingf("verifying installation")
|
||||
if err := statusChecker.Assess(components...); err != nil {
|
||||
if err := statusChecker.Assess(componentRefs...); err != nil {
|
||||
return fmt.Errorf("install failed")
|
||||
}
|
||||
|
||||
|
||||
261
cmd/flux/logs.go
Normal file
261
cmd/flux/logs.go
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
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 (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
)
|
||||
|
||||
var logsCmd = &cobra.Command{
|
||||
Use: "logs",
|
||||
Short: "Display formatted logs for Flux components",
|
||||
Long: "The logs command displays formatted logs from various Flux components.",
|
||||
Example: ` # Print the reconciliation logs of all Flux custom resources in your cluster
|
||||
flux logs --all-namespaces
|
||||
|
||||
# Stream logs for a particular log level
|
||||
flux logs --follow --level=error --all-namespaces
|
||||
|
||||
# Filter logs by kind, name and namespace
|
||||
flux logs --kind=Kustomization --name=podinfo --namespace=default
|
||||
|
||||
# Print logs when Flux is installed in a different namespace than flux-system
|
||||
flux logs --flux-namespace=my-namespace
|
||||
`,
|
||||
RunE: logsCmdRun,
|
||||
}
|
||||
|
||||
type logsFlags struct {
|
||||
logLevel flags.LogLevel
|
||||
follow bool
|
||||
tail int64
|
||||
kind string
|
||||
name string
|
||||
fluxNamespace string
|
||||
allNamespaces bool
|
||||
}
|
||||
|
||||
var logsArgs = &logsFlags{
|
||||
tail: -1,
|
||||
}
|
||||
|
||||
func init() {
|
||||
logsCmd.Flags().Var(&logsArgs.logLevel, "level", logsArgs.logLevel.Description())
|
||||
logsCmd.Flags().StringVarP(&logsArgs.kind, "kind", "", logsArgs.kind, "displays errors of a particular toolkit kind e.g GitRepository")
|
||||
logsCmd.Flags().StringVarP(&logsArgs.name, "name", "", logsArgs.name, "specifies the name of the object logs to be displayed")
|
||||
logsCmd.Flags().BoolVarP(&logsArgs.follow, "follow", "f", logsArgs.follow, "specifies if the logs should be streamed")
|
||||
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().BoolVarP(&logsArgs.allNamespaces, "all-namespaces", "A", false, "displays logs for objects across all namespaces")
|
||||
rootCmd.AddCommand(logsCmd)
|
||||
}
|
||||
|
||||
func logsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
fluxSelector := fmt.Sprintf("app.kubernetes.io/instance=%s", logsArgs.fluxNamespace)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
var pods []corev1.Pod
|
||||
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientset, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("no argument required")
|
||||
}
|
||||
|
||||
pods, err = getPods(ctx, clientset, fluxSelector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logOpts := &corev1.PodLogOptions{
|
||||
Follow: logsArgs.follow,
|
||||
}
|
||||
|
||||
if logsArgs.tail > -1 {
|
||||
logOpts.TailLines = &logsArgs.tail
|
||||
}
|
||||
|
||||
var requests []rest.ResponseWrapper
|
||||
for _, pod := range pods {
|
||||
req := clientset.CoreV1().Pods(logsArgs.fluxNamespace).GetLogs(pod.Name, logOpts)
|
||||
requests = append(requests, req)
|
||||
}
|
||||
|
||||
if logsArgs.follow && len(requests) > 1 {
|
||||
return parallelPodLogs(ctx, requests)
|
||||
}
|
||||
|
||||
return podLogs(ctx, requests)
|
||||
}
|
||||
|
||||
func getPods(ctx context.Context, c *kubernetes.Clientset, label string) ([]corev1.Pod, error) {
|
||||
var ret []corev1.Pod
|
||||
|
||||
opts := metav1.ListOptions{
|
||||
LabelSelector: label,
|
||||
}
|
||||
deployList, err := c.AppsV1().Deployments(logsArgs.fluxNamespace).List(ctx, opts)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
for _, deploy := range deployList.Items {
|
||||
label := deploy.Spec.Template.Labels
|
||||
opts := metav1.ListOptions{
|
||||
LabelSelector: createLabelStringFromMap(label),
|
||||
}
|
||||
podList, err := c.CoreV1().Pods(logsArgs.fluxNamespace).List(ctx, opts)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
ret = append(ret, podList.Items...)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parallelPodLogs(ctx context.Context, requests []rest.ResponseWrapper) error {
|
||||
reader, writer := io.Pipe()
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(requests))
|
||||
|
||||
var mutex = &sync.Mutex{}
|
||||
|
||||
for _, request := range requests {
|
||||
go func(req rest.ResponseWrapper) {
|
||||
defer wg.Done()
|
||||
if err := logRequest(mutex, ctx, req, os.Stdout); err != nil {
|
||||
writer.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
}(request)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
writer.Close()
|
||||
}()
|
||||
|
||||
_, err := io.Copy(os.Stdout, reader)
|
||||
return err
|
||||
}
|
||||
|
||||
func podLogs(ctx context.Context, requests []rest.ResponseWrapper) error {
|
||||
mutex := &sync.Mutex{}
|
||||
for _, req := range requests {
|
||||
if err := logRequest(mutex, ctx, req, os.Stdout); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createLabelStringFromMap(m map[string]string) string {
|
||||
var strArr []string
|
||||
for key, val := range m {
|
||||
pair := fmt.Sprintf("%v=%v", key, val)
|
||||
strArr = append(strArr, pair)
|
||||
}
|
||||
|
||||
return strings.Join(strArr, ",")
|
||||
}
|
||||
|
||||
func logRequest(mu *sync.Mutex, ctx context.Context, request rest.ResponseWrapper, w io.Writer) error {
|
||||
stream, err := request.Stream(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
scanner := bufio.NewScanner(stream)
|
||||
|
||||
const logTmpl = "{{.Timestamp}} {{.Level}} {{.Kind}}{{if .Name}}/{{.Name}}.{{.Namespace}}{{end}} - {{.Message}} {{.Error}}\n"
|
||||
t, err := template.New("log").Parse(logTmpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create template, err: %s", err)
|
||||
}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if !strings.HasPrefix(line, "{") {
|
||||
continue
|
||||
}
|
||||
var l ControllerLogEntry
|
||||
if err := json.Unmarshal([]byte(line), &l); err != nil {
|
||||
logger.Failuref("parse error: %s", err)
|
||||
break
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
filterPrintLog(t, &l)
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func filterPrintLog(t *template.Template, l *ControllerLogEntry) {
|
||||
if logsArgs.logLevel != "" && logsArgs.logLevel != l.Level ||
|
||||
logsArgs.kind != "" && strings.ToLower(logsArgs.kind) != strings.ToLower(l.Kind) ||
|
||||
logsArgs.name != "" && strings.ToLower(logsArgs.name) != strings.ToLower(l.Name) ||
|
||||
!logsArgs.allNamespaces && strings.ToLower(rootArgs.namespace) != strings.ToLower(l.Namespace) {
|
||||
return
|
||||
}
|
||||
|
||||
err := t.Execute(os.Stdout, l)
|
||||
if err != nil {
|
||||
logger.Failuref("log template error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
type ControllerLogEntry struct {
|
||||
Timestamp string `json:"ts"`
|
||||
Level flags.LogLevel `json:"level"`
|
||||
Message string `json:"msg"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Logger string `json:"logger"`
|
||||
Kind string `json:"reconciler kind,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
@@ -41,7 +41,7 @@ var rootCmd = &cobra.Command{
|
||||
Example: ` # Check prerequisites
|
||||
flux check --pre
|
||||
|
||||
# Install the latest version of the toolkit
|
||||
# Install the latest version of Flux
|
||||
flux install --version=master
|
||||
|
||||
# Create a source from a public Git repository
|
||||
@@ -88,8 +88,8 @@ var rootCmd = &cobra.Command{
|
||||
# Delete a GitRepository source
|
||||
flux delete source git webapp-latest
|
||||
|
||||
# Uninstall the toolkit and delete CRDs
|
||||
flux uninstall --crds
|
||||
# Uninstall Flux and delete CRDs
|
||||
flux uninstall
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -115,10 +115,12 @@ func init() {
|
||||
}
|
||||
|
||||
func NewRootFlags() rootFlags {
|
||||
return rootFlags{
|
||||
rf := rootFlags{
|
||||
pollInterval: 2 * time.Second,
|
||||
defaults: install.MakeDefaultOptions(),
|
||||
}
|
||||
rf.defaults.Version = "v" + VERSION
|
||||
return rf
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
31
cmd/flux/manifests.embed.go
Normal file
31
cmd/flux/manifests.embed.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
//go:embed manifests/*.yaml
|
||||
var embeddedManifests embed.FS
|
||||
|
||||
func writeEmbeddedManifests(dir string) error {
|
||||
manifests, err := fs.ReadDir(embeddedManifests, "manifests")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, manifest := range manifests {
|
||||
data, err := fs.ReadFile(embeddedManifests, path.Join("manifests", manifest.Name()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading file failed: %w", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(path.Join(dir, manifest.Name()), data, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing file failed: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
@@ -62,6 +63,7 @@ func (c universalAdapter) asClientObject() client.Object {
|
||||
type named interface {
|
||||
GetName() string
|
||||
GetNamespace() string
|
||||
GetObjectKind() schema.ObjectKind
|
||||
SetName(string)
|
||||
SetNamespace(string)
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (a helmChartAdapter) asClientObject() client.Object {
|
||||
return a.HelmChart
|
||||
}
|
||||
|
||||
// sourcev1.ImagePolicyList
|
||||
// sourcev1.HelmChartList
|
||||
|
||||
type helmChartListAdapter struct {
|
||||
*sourcev1.HelmChartList
|
||||
|
||||
@@ -19,24 +19,15 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling/aggregator"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling/collector"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
)
|
||||
|
||||
@@ -50,13 +41,6 @@ type statusable interface {
|
||||
GetStatusConditions() *[]metav1.Condition
|
||||
}
|
||||
|
||||
type StatusChecker struct {
|
||||
pollInterval time.Duration
|
||||
timeout time.Duration
|
||||
client client.Client
|
||||
statusPoller *polling.StatusPoller
|
||||
}
|
||||
|
||||
func isReady(ctx context.Context, kubeClient client.Client,
|
||||
namespacedName types.NamespacedName, object statusable) wait.ConditionFunc {
|
||||
return func() (bool, error) {
|
||||
@@ -82,74 +66,7 @@ func isReady(ctx context.Context, kubeClient client.Client,
|
||||
}
|
||||
}
|
||||
|
||||
func NewStatusChecker(pollInterval time.Duration, timeout time.Duration) (*StatusChecker, error) {
|
||||
kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
restMapper, err := apiutil.NewDynamicRESTMapper(kubeConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := client.New(kubeConfig, client.Options{Mapper: restMapper})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &StatusChecker{
|
||||
pollInterval: pollInterval,
|
||||
timeout: timeout,
|
||||
client: client,
|
||||
statusPoller: polling.NewStatusPoller(client, restMapper),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (sc *StatusChecker) Assess(components ...string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), sc.timeout)
|
||||
defer cancel()
|
||||
|
||||
objRefs, err := sc.getObjectRefs(components)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := polling.Options{PollInterval: sc.pollInterval, UseCache: true}
|
||||
eventsChan := sc.statusPoller.Poll(ctx, objRefs, opts)
|
||||
|
||||
coll := collector.NewResourceStatusCollector(objRefs)
|
||||
done := coll.ListenWithObserver(eventsChan, collector.ObserverFunc(
|
||||
func(statusCollector *collector.ResourceStatusCollector, e event.Event) {
|
||||
var rss []*event.ResourceStatus
|
||||
for _, rs := range statusCollector.ResourceStatuses {
|
||||
rss = append(rss, rs)
|
||||
}
|
||||
desired := status.CurrentStatus
|
||||
aggStatus := aggregator.AggregateStatus(rss, desired)
|
||||
if aggStatus == desired {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}),
|
||||
)
|
||||
<-done
|
||||
|
||||
if coll.Error != nil || ctx.Err() == context.DeadlineExceeded {
|
||||
for _, rs := range coll.ResourceStatuses {
|
||||
if rs.Status != status.CurrentStatus {
|
||||
if !sc.deploymentExists(rs.Identifier) {
|
||||
logger.Failuref("%s: deployment not found", rs.Identifier.Name)
|
||||
} else {
|
||||
logger.Failuref("%s: unhealthy (timed out waiting for rollout)", rs.Identifier.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("timed out waiting for condition")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *StatusChecker) getObjectRefs(components []string) ([]object.ObjMetadata, error) {
|
||||
func buildComponentObjectRefs(components ...string) ([]object.ObjMetadata, error) {
|
||||
var objRefs []object.ObjMetadata
|
||||
for _, deployment := range components {
|
||||
objMeta, err := object.CreateObjMetadata(rootArgs.namespace, deployment, schema.GroupKind{Group: "apps", Kind: "Deployment"})
|
||||
@@ -160,20 +77,3 @@ func (sc *StatusChecker) getObjectRefs(components []string) ([]object.ObjMetadat
|
||||
}
|
||||
return objRefs, nil
|
||||
}
|
||||
|
||||
func (sc *StatusChecker) objMetadataToString(om object.ObjMetadata) string {
|
||||
return fmt.Sprintf("%s '%s/%s'", om.GroupKind.Kind, om.Namespace, om.Name)
|
||||
}
|
||||
|
||||
func (sc *StatusChecker) deploymentExists(om object.ObjMetadata) bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), sc.timeout)
|
||||
defer cancel()
|
||||
|
||||
namespacedName := types.NamespacedName{
|
||||
Namespace: om.Namespace,
|
||||
Name: om.Name,
|
||||
}
|
||||
var existing appsv1.Deployment
|
||||
err := sc.client.Get(ctx, namespacedName, &existing)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -22,8 +22,12 @@ import (
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/spf13/cobra"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
@@ -34,33 +38,30 @@ import (
|
||||
|
||||
var uninstallCmd = &cobra.Command{
|
||||
Use: "uninstall",
|
||||
Short: "Uninstall the toolkit components",
|
||||
Long: "The uninstall command removes the namespace, cluster roles, cluster role bindings and CRDs from the cluster.",
|
||||
Example: ` # Dry-run uninstall of all components
|
||||
flux uninstall --dry-run --namespace=flux-system
|
||||
Short: "Uninstall Flux and its custom resource definitions",
|
||||
Long: "The uninstall command removes the Flux components and the toolkit.fluxcd.io resources from the cluster.",
|
||||
Example: ` # Uninstall Flux components, its custom resources and namespace
|
||||
flux uninstall --namespace=flux-system
|
||||
|
||||
# Uninstall all components and delete custom resource definitions
|
||||
flux uninstall --resources --crds --namespace=flux-system
|
||||
# Uninstall Flux but keep the namespace
|
||||
flux uninstall --namespace=infra --keep-namespace=true
|
||||
`,
|
||||
RunE: uninstallCmdRun,
|
||||
}
|
||||
|
||||
type uninstallFlags struct {
|
||||
crds bool
|
||||
resources bool
|
||||
dryRun bool
|
||||
silent bool
|
||||
keepNamespace bool
|
||||
dryRun bool
|
||||
silent bool
|
||||
}
|
||||
|
||||
var uninstallArgs uninstallFlags
|
||||
|
||||
func init() {
|
||||
uninstallCmd.Flags().BoolVar(&uninstallArgs.resources, "resources", true,
|
||||
"removes custom resources such as Kustomizations, GitRepositories and HelmRepositories")
|
||||
uninstallCmd.Flags().BoolVar(&uninstallArgs.crds, "crds", false,
|
||||
"removes all CRDs previously installed")
|
||||
uninstallCmd.Flags().BoolVar(&uninstallArgs.keepNamespace, "keep-namespace", false,
|
||||
"skip namespace deletion")
|
||||
uninstallCmd.Flags().BoolVar(&uninstallArgs.dryRun, "dry-run", false,
|
||||
"only print the object that would be deleted")
|
||||
"only print the objects that would be deleted")
|
||||
uninstallCmd.Flags().BoolVarP(&uninstallArgs.silent, "silent", "s", false,
|
||||
"delete components without asking for confirmation")
|
||||
|
||||
@@ -68,6 +69,16 @@ func init() {
|
||||
}
|
||||
|
||||
func uninstallCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if !uninstallArgs.dryRun && !uninstallArgs.silent {
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Are you sure you want to delete Flux and its custom resource definitions",
|
||||
IsConfirm: true,
|
||||
}
|
||||
if _, err := prompt.Run(); err != nil {
|
||||
return fmt.Errorf("aborting")
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
@@ -76,96 +87,227 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !uninstallArgs.dryRun && !uninstallArgs.silent {
|
||||
prompt := promptui.Prompt{
|
||||
Label: fmt.Sprintf("Are you sure you want to delete the %s namespace", rootArgs.namespace),
|
||||
IsConfirm: true,
|
||||
}
|
||||
if _, err := prompt.Run(); err != nil {
|
||||
return fmt.Errorf("aborting")
|
||||
}
|
||||
}
|
||||
logger.Actionf("deleting components in %s namespace", rootArgs.namespace)
|
||||
uninstallComponents(ctx, kubeClient, rootArgs.namespace, uninstallArgs.dryRun)
|
||||
|
||||
dryRun := "--dry-run=server"
|
||||
deleteResources := uninstallArgs.resources || uninstallArgs.crds
|
||||
logger.Actionf("deleting toolkit.fluxcd.io finalizers in all namespaces")
|
||||
uninstallFinalizers(ctx, kubeClient, uninstallArgs.dryRun)
|
||||
|
||||
// known kinds with finalizers
|
||||
namespacedKinds := []string{
|
||||
sourcev1.GitRepositoryKind,
|
||||
sourcev1.HelmRepositoryKind,
|
||||
sourcev1.BucketKind,
|
||||
}
|
||||
logger.Actionf("deleting toolkit.fluxcd.io custom resource definitions")
|
||||
uninstallCustomResourceDefinitions(ctx, kubeClient, rootArgs.namespace, uninstallArgs.dryRun)
|
||||
|
||||
// suspend bootstrap kustomization to avoid finalizers deadlock
|
||||
kustomizationName := types.NamespacedName{
|
||||
Namespace: rootArgs.namespace,
|
||||
Name: rootArgs.namespace,
|
||||
}
|
||||
var kustomization kustomizev1.Kustomization
|
||||
err = kubeClient.Get(ctx, kustomizationName, &kustomization)
|
||||
if err == nil {
|
||||
kustomization.Spec.Suspend = true
|
||||
if err := kubeClient.Update(ctx, &kustomization); err != nil {
|
||||
return fmt.Errorf("unable to suspend kustomization '%s': %w", kustomizationName.String(), err)
|
||||
}
|
||||
}
|
||||
if err == nil || apierrors.IsNotFound(err) {
|
||||
namespacedKinds = append(namespacedKinds, kustomizev1.KustomizationKind)
|
||||
}
|
||||
|
||||
// add HelmRelease kind to deletion list if exists
|
||||
var list helmv2.HelmReleaseList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace)); err == nil {
|
||||
namespacedKinds = append(namespacedKinds, helmv2.HelmReleaseKind)
|
||||
}
|
||||
|
||||
if deleteResources {
|
||||
logger.Actionf("uninstalling custom resources")
|
||||
for _, kind := range namespacedKinds {
|
||||
if err := deleteAll(ctx, kind, uninstallArgs.dryRun); err != nil {
|
||||
logger.Failuref("kubectl: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var kinds []string
|
||||
if uninstallArgs.crds {
|
||||
kinds = append(kinds, "crds")
|
||||
}
|
||||
|
||||
kinds = append(kinds, "clusterroles,clusterrolebindings", "namespace")
|
||||
|
||||
logger.Actionf("uninstalling components")
|
||||
|
||||
for _, kind := range kinds {
|
||||
kubectlArgs := []string{
|
||||
"delete", kind,
|
||||
"-l", fmt.Sprintf("app.kubernetes.io/instance=%s", rootArgs.namespace),
|
||||
"--ignore-not-found", "--timeout", rootArgs.timeout.String(),
|
||||
}
|
||||
if uninstallArgs.dryRun {
|
||||
kubectlArgs = append(kubectlArgs, dryRun)
|
||||
}
|
||||
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
|
||||
return fmt.Errorf("uninstall failed: %w", err)
|
||||
}
|
||||
if !uninstallArgs.keepNamespace {
|
||||
uninstallNamespace(ctx, kubeClient, rootArgs.namespace, uninstallArgs.dryRun)
|
||||
}
|
||||
|
||||
logger.Successf("uninstall finished")
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteAll(ctx context.Context, kind string, dryRun bool) error {
|
||||
kubectlArgs := []string{
|
||||
"delete", kind, "--ignore-not-found",
|
||||
"--all", "--all-namespaces",
|
||||
"--timeout", rootArgs.timeout.String(),
|
||||
func uninstallComponents(ctx context.Context, kubeClient client.Client, namespace string, dryRun bool) {
|
||||
opts, dryRunStr := getDeleteOptions(dryRun)
|
||||
selector := client.MatchingLabels{"app.kubernetes.io/instance": namespace}
|
||||
{
|
||||
var list appsv1.DeploymentList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {
|
||||
for _, r := range list.Items {
|
||||
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("Deployment/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("Deployment/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
kubectlArgs = append(kubectlArgs, "--dry-run=server")
|
||||
{
|
||||
var list corev1.ServiceList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {
|
||||
for _, r := range list.Items {
|
||||
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("Service/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("Service/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var list networkingv1.NetworkPolicyList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {
|
||||
for _, r := range list.Items {
|
||||
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("NetworkPolicy/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("NetworkPolicy/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var list corev1.ServiceAccountList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {
|
||||
for _, r := range list.Items {
|
||||
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("ServiceAccount/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("ServiceAccount/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var list rbacv1.ClusterRoleList
|
||||
if err := kubeClient.List(ctx, &list, selector); err == nil {
|
||||
for _, r := range list.Items {
|
||||
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("ClusterRole/%s deletion failed: %s", r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("ClusterRole/%s deleted %s", r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var list rbacv1.ClusterRoleBindingList
|
||||
if err := kubeClient.List(ctx, &list, selector); err == nil {
|
||||
for _, r := range list.Items {
|
||||
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("ClusterRoleBinding/%s deletion failed: %s", r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("ClusterRoleBinding/%s deleted %s", r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
|
||||
return err
|
||||
}
|
||||
|
||||
func uninstallFinalizers(ctx context.Context, kubeClient client.Client, dryRun bool) {
|
||||
opts, dryRunStr := getUpdateOptions(dryRun)
|
||||
{
|
||||
var list sourcev1.GitRepositoryList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
|
||||
for _, r := range list.Items {
|
||||
r.Finalizers = []string{}
|
||||
if err := kubeClient.Update(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var list sourcev1.HelmRepositoryList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
|
||||
for _, r := range list.Items {
|
||||
r.Finalizers = []string{}
|
||||
if err := kubeClient.Update(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var list sourcev1.HelmChartList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
|
||||
for _, r := range list.Items {
|
||||
r.Finalizers = []string{}
|
||||
if err := kubeClient.Update(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var list sourcev1.BucketList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
|
||||
for _, r := range list.Items {
|
||||
r.Finalizers = []string{}
|
||||
if err := kubeClient.Update(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var list kustomizev1.KustomizationList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
|
||||
for _, r := range list.Items {
|
||||
r.Finalizers = []string{}
|
||||
if err := kubeClient.Update(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var list helmv2.HelmReleaseList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
|
||||
for _, r := range list.Items {
|
||||
r.Finalizers = []string{}
|
||||
if err := kubeClient.Update(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func uninstallCustomResourceDefinitions(ctx context.Context, kubeClient client.Client, namespace string, dryRun bool) {
|
||||
opts, dryRunStr := getDeleteOptions(dryRun)
|
||||
selector := client.MatchingLabels{"app.kubernetes.io/instance": namespace}
|
||||
{
|
||||
var list apiextensionsv1.CustomResourceDefinitionList
|
||||
if err := kubeClient.List(ctx, &list, selector); err == nil {
|
||||
for _, r := range list.Items {
|
||||
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
|
||||
logger.Failuref("CustomResourceDefinition/%s deletion failed: %s", r.Name, err.Error())
|
||||
} else {
|
||||
logger.Successf("CustomResourceDefinition/%s deleted %s", r.Name, dryRunStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func uninstallNamespace(ctx context.Context, kubeClient client.Client, namespace string, dryRun bool) {
|
||||
opts, dryRunStr := getDeleteOptions(dryRun)
|
||||
ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
|
||||
if err := kubeClient.Delete(ctx, &ns, opts); err != nil {
|
||||
logger.Failuref("Namespace/%s deletion failed: %s", namespace, err.Error())
|
||||
} else {
|
||||
logger.Successf("Namespace/%s deleted %s", namespace, dryRunStr)
|
||||
}
|
||||
}
|
||||
|
||||
func getDeleteOptions(dryRun bool) (*client.DeleteOptions, string) {
|
||||
opts := &client.DeleteOptions{}
|
||||
var dryRunStr string
|
||||
if dryRun {
|
||||
client.DryRunAll.ApplyToDelete(opts)
|
||||
dryRunStr = "(dry run)"
|
||||
}
|
||||
|
||||
return opts, dryRunStr
|
||||
}
|
||||
|
||||
func getUpdateOptions(dryRun bool) (*client.UpdateOptions, string) {
|
||||
opts := &client.UpdateOptions{}
|
||||
var dryRunStr string
|
||||
if dryRun {
|
||||
client.DryRunAll.ApplyToUpdate(opts)
|
||||
dryRunStr = "(dry run)"
|
||||
}
|
||||
|
||||
return opts, dryRunStr
|
||||
}
|
||||
|
||||
42
cmd/flux/version.go
Normal file
42
cmd/flux/version.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
)
|
||||
|
||||
func getVersion(input string) (string, error) {
|
||||
if input == "" {
|
||||
return rootArgs.defaults.Version, nil
|
||||
}
|
||||
|
||||
if isEmbeddedVersion(input) {
|
||||
return input, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if input == install.MakeDefaultOptions().Version {
|
||||
input, err = install.GetLatestVersion()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
if ok, err := install.ExistingVersion(input); err != nil || !ok {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("targeted version '%s' does not exist", input)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if !utils.CompatibleVersion(VERSION, input) {
|
||||
return "", fmt.Errorf("targeted version '%s' is not compatible with your current version of flux (%s)", input, VERSION)
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func isEmbeddedVersion(input string) bool {
|
||||
return input == rootArgs.defaults.Version
|
||||
}
|
||||
BIN
docs/_files/image-update-automation.png
Normal file
BIN
docs/_files/image-update-automation.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
27
docs/_static/custom.css
vendored
27
docs/_static/custom.css
vendored
@@ -94,4 +94,29 @@ body {
|
||||
|
||||
.progress-0plus .progress-bar {
|
||||
background-color: #ff1744;
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom admonitions */
|
||||
/* See https://squidfunk.github.io/mkdocs-material/reference/admonitions */
|
||||
:root {
|
||||
--md-admonition-icon--heart: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14 20.408c-.492.308-.903.546-1.192.709-.153.086-.308.17-.463.252h-.002a.75.75 0 0 1-.686 0 16.709 16.709 0 0 1-.465-.252 31.147 31.147 0 0 1-4.803-3.34C3.8 15.572 1 12.331 1 8.513 1 5.052 3.829 2.5 6.736 2.5 9.03 2.5 10.881 3.726 12 5.605 13.12 3.726 14.97 2.5 17.264 2.5 20.17 2.5 23 5.052 23 8.514c0 3.818-2.801 7.06-5.389 9.262A31.146 31.146 0 0 1 14 20.408z"/></svg>')
|
||||
}
|
||||
.md-typeset .admonition.heart,
|
||||
.md-typeset details.heart {
|
||||
border-color: rgb(233, 30, 99);
|
||||
}
|
||||
.md-typeset .heart > .admonition-title,
|
||||
.md-typeset .heart > summary {
|
||||
background-color: rgba(233, 30, 99, 0.1);
|
||||
}
|
||||
.md-typeset .heart > .admonition-title::before,
|
||||
.md-typeset .heart > summary::before {
|
||||
background-color: rgb(233, 30, 99);
|
||||
-webkit-mask-image: var(--md-admonition-icon--heart);
|
||||
mask-image: var(--md-admonition-icon--heart);
|
||||
}
|
||||
|
||||
.timetable-explicit-col-widths th:nth-child(1) { width: 4%; }
|
||||
.timetable-explicit-col-widths th:nth-child(2) { width: 32%; }
|
||||
.timetable-explicit-col-widths th:nth-child(3) { width: 32%; }
|
||||
.timetable-explicit-col-widths th:nth-child(4) { width: 32%; }
|
||||
|
||||
@@ -12,7 +12,7 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.
|
||||
# Check prerequisites
|
||||
flux check --pre
|
||||
|
||||
# Install the latest version of the toolkit
|
||||
# Install the latest version of Flux
|
||||
flux install --version=master
|
||||
|
||||
# Create a source from a public Git repository
|
||||
@@ -59,8 +59,8 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.
|
||||
# Delete a GitRepository source
|
||||
flux delete source git webapp-latest
|
||||
|
||||
# Uninstall the toolkit and delete CRDs
|
||||
flux uninstall --crds
|
||||
# Uninstall Flux and delete CRDs
|
||||
flux uninstall
|
||||
|
||||
```
|
||||
|
||||
@@ -83,10 +83,11 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.
|
||||
* [flux create](flux_create.md) - Create or update sources and resources
|
||||
* [flux delete](flux_delete.md) - Delete sources and resources
|
||||
* [flux export](flux_export.md) - Export resources in YAML format
|
||||
* [flux get](flux_get.md) - Get sources and resources
|
||||
* [flux install](flux_install.md) - Install the toolkit components
|
||||
* [flux get](flux_get.md) - Get the resources and their status
|
||||
* [flux install](flux_install.md) - Install or upgrade Flux
|
||||
* [flux logs](flux_logs.md) - Display formatted logs for Flux components
|
||||
* [flux reconcile](flux_reconcile.md) - Reconcile sources and resources
|
||||
* [flux resume](flux_resume.md) - Resume suspended resources
|
||||
* [flux suspend](flux_suspend.md) - Suspend resources
|
||||
* [flux uninstall](flux_uninstall.md) - Uninstall the toolkit components
|
||||
* [flux uninstall](flux_uninstall.md) - Uninstall Flux and its custom resource definitions
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ The bootstrap sub-commands bootstrap the toolkit components on the targeted Git
|
||||
--network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
|
||||
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd")
|
||||
--token-auth when enabled, the personal access token will be used instead of SSH deploy key
|
||||
-v, --version string toolkit version (default "latest")
|
||||
--toleration-keys strings list of toleration keys used to schedule the components pods onto nodes with matching taints
|
||||
-v, --version string toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
|
||||
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
|
||||
```
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ flux bootstrap github [flags]
|
||||
--interval duration sync interval (default 1m0s)
|
||||
--owner string GitHub user or organization name
|
||||
--path safeRelativePath path relative to the repository root, when specified the cluster sync will be scoped to this path
|
||||
--personal is personal repository
|
||||
--private is private repository (default true)
|
||||
--personal if true, the owner is assumed to be a GitHub user; otherwise an org
|
||||
--private if true, the repository is assumed to be private (default true)
|
||||
--repository string GitHub repository name
|
||||
--ssh-hostname string GitHub SSH hostname, to be used when the SSH host differs from the HTTPS one
|
||||
--team stringArray GitHub team to be given maintainer access
|
||||
@@ -74,8 +74,9 @@ flux bootstrap github [flags]
|
||||
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd")
|
||||
--timeout duration timeout for this operation (default 5m0s)
|
||||
--token-auth when enabled, the personal access token will be used instead of SSH deploy key
|
||||
--toleration-keys strings list of toleration keys used to schedule the components pods onto nodes with matching taints
|
||||
--verbose print generated objects
|
||||
-v, --version string toolkit version (default "latest")
|
||||
-v, --version string toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
|
||||
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
|
||||
```
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ flux bootstrap gitlab [flags]
|
||||
--interval duration sync interval (default 1m0s)
|
||||
--owner string GitLab user or group name
|
||||
--path safeRelativePath path relative to the repository root, when specified the cluster sync will be scoped to this path
|
||||
--personal is personal repository
|
||||
--private is private repository (default true)
|
||||
--personal if true, the owner is assumed to be a GitLab user; otherwise a group
|
||||
--private if true, the repository is assumed to be private (default true)
|
||||
--repository string GitLab repository name
|
||||
--ssh-hostname string GitLab SSH hostname, to be used when the SSH host differs from the HTTPS one
|
||||
```
|
||||
@@ -70,8 +70,9 @@ flux bootstrap gitlab [flags]
|
||||
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd")
|
||||
--timeout duration timeout for this operation (default 5m0s)
|
||||
--token-auth when enabled, the personal access token will be used instead of SSH deploy key
|
||||
--toleration-keys strings list of toleration keys used to schedule the components pods onto nodes with matching taints
|
||||
--verbose print generated objects
|
||||
-v, --version string toolkit version (default "latest")
|
||||
-v, --version string toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
|
||||
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
|
||||
```
|
||||
|
||||
|
||||
@@ -9,13 +9,9 @@ flux completion fish [flags]
|
||||
### Examples
|
||||
|
||||
```
|
||||
To load completion run
|
||||
|
||||
. <(flux completion fish)
|
||||
|
||||
To configure your fish shell to load completions for each session write this script to your completions dir:
|
||||
|
||||
flux completion fish > ~/.config/fish/completions/flux
|
||||
flux completion fish > ~/.config/fish/completions/flux.fish
|
||||
|
||||
See http://fishshell.com/docs/current/index.html#completion-own for more details
|
||||
|
||||
|
||||
@@ -12,16 +12,36 @@ The image that sorts highest according to the policy is recorded in
|
||||
the status of the object.
|
||||
|
||||
```
|
||||
flux create image policy <name> [flags]
|
||||
flux create image policy [name] [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
# Create an ImagePolicy to select the latest stable release
|
||||
flux create image policy podinfo \
|
||||
--image-ref=podinfo \
|
||||
--select-semver=">=1.0.0"
|
||||
|
||||
# Create an ImagePolicy to select the latest main branch build tagged as "${GIT_BRANCH}-${GIT_SHA:0:7}-$(date +%s)"
|
||||
flux create image policy podinfo \
|
||||
--image-ref=podinfo \
|
||||
--select-numeric=asc \
|
||||
--filter-regex='^main-[a-f0-9]+-(?P<ts>[0-9]+)' \
|
||||
--filter-extract='$ts'
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--filter-regex string regular expression pattern used to filter the image tags
|
||||
-h, --help help for policy
|
||||
--image-ref string the name of an image repository object
|
||||
--semver string a semver range to apply to tags; e.g., '1.x'
|
||||
--filter-extract string replacement pattern (using capture groups from --filter-regex) to use for sorting
|
||||
--filter-regex string regular expression pattern used to filter the image tags
|
||||
-h, --help help for policy
|
||||
--image-ref string the name of an image repository object
|
||||
--select-alpha string use alphabetical sorting to select image; either "asc" meaning select the last, or "desc" meaning select the first
|
||||
--select-numeric string use numeric sorting to select image; either "asc" meaning select the last, or "desc" meaning select the first
|
||||
--select-semver string a semver range to apply to tags; e.g., '1.x'
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -8,7 +8,7 @@ The create image repository command generates an ImageRepository resource.
|
||||
An ImageRepository object specifies an image repository to scan.
|
||||
|
||||
```
|
||||
flux create image repository <name> [flags]
|
||||
flux create image repository [name] [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -9,7 +9,31 @@ An ImageUpdateAutomation object specifies an automated update to images
|
||||
mentioned in YAMLs in a git repository.
|
||||
|
||||
```
|
||||
flux create image update <name> [flags]
|
||||
flux create image update [name] [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
# Configure image updates for the main repository created by flux bootstrap
|
||||
flux create image update flux-system \
|
||||
--git-repo-ref=flux-system \
|
||||
--git-repo-path="./clusters/my-cluster" \
|
||||
--checkout-branch=main \
|
||||
--author-name=flux \
|
||||
--author-email=flux@example.com \
|
||||
--commit-template="{{range .Updated.Images}}{{println .}}{{end}}"
|
||||
|
||||
# Configure image updates to push changes to a different branch, if the branch doesn't exists it will be created
|
||||
flux create image update flux-system \
|
||||
--git-repo-ref=flux-system \
|
||||
--git-repo-path="./clusters/my-cluster" \
|
||||
--checkout-branch=main \
|
||||
--push-branch=image-updates \
|
||||
--author-name=flux \
|
||||
--author-email=flux@example.com \
|
||||
--commit-template="{{range .Updated.Images}}{{println .}}{{end}}"
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
@@ -17,10 +41,12 @@ flux create image update <name> [flags]
|
||||
```
|
||||
--author-email string the email to use for commit author
|
||||
--author-name string the name to use for commit author
|
||||
--branch string the branch to checkout and push commits to
|
||||
--checkout-branch string the branch to checkout
|
||||
--commit-template string a template for commit messages
|
||||
--git-repo-ref string the name of a GitRepository resource with details of the upstream git repository
|
||||
--git-repo-path string path to the directory containing the manifests to be updated, defaults to the repository root
|
||||
--git-repo-ref string the name of a GitRepository resource with details of the upstream Git repository
|
||||
-h, --help help for update
|
||||
--push-branch string the branch to push commits to, defaults to the checkout branch if not specified
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -50,6 +50,7 @@ flux create secret git [name] [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
--ca-file string path to TLS CA file used for validating self-signed certificates
|
||||
-h, --help help for git
|
||||
-p, --password string basic authentication password
|
||||
--ssh-ecdsa-curve ecdsaCurve SSH ECDSA public key curve (p256, p384, p521) (default p384)
|
||||
|
||||
@@ -14,8 +14,8 @@ flux create secret helm [name] [flags]
|
||||
### Examples
|
||||
|
||||
```
|
||||
# Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS
|
||||
|
||||
# Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS
|
||||
flux create secret helm repo-auth \
|
||||
--namespace=my-namespace \
|
||||
--username=my-username \
|
||||
|
||||
@@ -56,6 +56,7 @@ flux create source git [name] [flags]
|
||||
|
||||
```
|
||||
--branch string git branch (default "master")
|
||||
--ca-file string path to TLS CA file used for validating self-signed certificates, requires libgit2
|
||||
--git-implementation gitImplementation the Git implementation to use, available options are: (go-git, libgit2)
|
||||
-h, --help help for git
|
||||
-p, --password string basic authentication password
|
||||
|
||||
@@ -4,7 +4,7 @@ Export Bucket sources in YAML format
|
||||
|
||||
### Synopsis
|
||||
|
||||
The export source git command exports on or all Bucket sources in YAML format.
|
||||
The export source git command exports one or all Bucket sources in YAML format.
|
||||
|
||||
```
|
||||
flux export source bucket [name] [flags]
|
||||
|
||||
@@ -4,7 +4,7 @@ Export GitRepository sources in YAML format
|
||||
|
||||
### Synopsis
|
||||
|
||||
The export source git command exports on or all GitRepository sources in YAML format.
|
||||
The export source git command exports one or all GitRepository sources in YAML format.
|
||||
|
||||
```
|
||||
flux export source git [name] [flags]
|
||||
|
||||
@@ -4,7 +4,7 @@ Export HelmRepository sources in YAML format
|
||||
|
||||
### Synopsis
|
||||
|
||||
The export source git command exports on or all HelmRepository sources in YAML format.
|
||||
The export source git command exports one or all HelmRepository sources in YAML format.
|
||||
|
||||
```
|
||||
flux export source helm [name] [flags]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
## flux get
|
||||
|
||||
Get sources and resources
|
||||
Get the resources and their status
|
||||
|
||||
### Synopsis
|
||||
|
||||
The get sub-commands print the statuses of sources and resources.
|
||||
The get sub-commands print the statuses of Flux resources.
|
||||
|
||||
### Options
|
||||
|
||||
|
||||
@@ -37,5 +37,5 @@ flux get alert-providers [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [flux get](flux_get.md) - Get sources and resources
|
||||
* [flux get](flux_get.md) - Get the resources and their status
|
||||
|
||||
|
||||
@@ -37,5 +37,5 @@ flux get alerts [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [flux get](flux_get.md) - Get sources and resources
|
||||
* [flux get](flux_get.md) - Get the resources and their status
|
||||
|
||||
|
||||
@@ -37,5 +37,5 @@ flux get helmreleases [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [flux get](flux_get.md) - Get sources and resources
|
||||
* [flux get](flux_get.md) - Get the resources and their status
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ The get image sub-commands print the status of image automation objects.
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [flux get](flux_get.md) - Get sources and resources
|
||||
* [flux get](flux_get.md) - Get the resources and their status
|
||||
* [flux get images all](flux_get_images_all.md) - Get all image statuses
|
||||
* [flux get images policy](flux_get_images_policy.md) - Get ImagePolicy status
|
||||
* [flux get images repository](flux_get_images_repository.md) - Get ImageRepository status
|
||||
* [flux get images update](flux_get_images_update.md) - Get ImageUpdateAutomation status
|
||||
|
||||
44
docs/cmd/flux_get_images_all.md
Normal file
44
docs/cmd/flux_get_images_all.md
Normal file
@@ -0,0 +1,44 @@
|
||||
## flux get images all
|
||||
|
||||
Get all image statuses
|
||||
|
||||
### Synopsis
|
||||
|
||||
The get image sub-commands print the statuses of all image objects.
|
||||
|
||||
```
|
||||
flux get images all [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
# List all image objects in a namespace
|
||||
flux get images all --namespace=flux-system
|
||||
|
||||
# List all image objects in all namespaces
|
||||
flux get images all --all-namespaces
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for all
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-A, --all-namespaces list the requested object(s) across all namespaces
|
||||
--context string kubernetes context to use
|
||||
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
|
||||
-n, --namespace string the namespace scope for this operation (default "flux-system")
|
||||
--timeout duration timeout for this operation (default 5m0s)
|
||||
--verbose print generated objects
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [flux get images](flux_get_images.md) - Get image automation object status
|
||||
|
||||
@@ -37,5 +37,5 @@ flux get kustomizations [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [flux get](flux_get.md) - Get sources and resources
|
||||
* [flux get](flux_get.md) - Get the resources and their status
|
||||
|
||||
|
||||
@@ -37,5 +37,5 @@ flux get receivers [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [flux get](flux_get.md) - Get sources and resources
|
||||
* [flux get](flux_get.md) - Get the resources and their status
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ The get source sub-commands print the statuses of the sources.
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [flux get](flux_get.md) - Get sources and resources
|
||||
* [flux get](flux_get.md) - Get the resources and their status
|
||||
* [flux get sources all](flux_get_sources_all.md) - Get all source statuses
|
||||
* [flux get sources bucket](flux_get_sources_bucket.md) - Get Bucket source statuses
|
||||
* [flux get sources chart](flux_get_sources_chart.md) - Get HelmChart statuses
|
||||
* [flux get sources git](flux_get_sources_git.md) - Get GitRepository source statuses
|
||||
|
||||
44
docs/cmd/flux_get_sources_all.md
Normal file
44
docs/cmd/flux_get_sources_all.md
Normal file
@@ -0,0 +1,44 @@
|
||||
## flux get sources all
|
||||
|
||||
Get all source statuses
|
||||
|
||||
### Synopsis
|
||||
|
||||
The get sources all command print the statuses of all sources.
|
||||
|
||||
```
|
||||
flux get sources all [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
# List all sources in a namespace
|
||||
flux get sources all --namespace=flux-system
|
||||
|
||||
# List all sources in all namespaces
|
||||
flux get sources all --all-namespaces
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for all
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-A, --all-namespaces list the requested object(s) across all namespaces
|
||||
--context string kubernetes context to use
|
||||
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
|
||||
-n, --namespace string the namespace scope for this operation (default "flux-system")
|
||||
--timeout duration timeout for this operation (default 5m0s)
|
||||
--verbose print generated objects
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [flux get sources](flux_get_sources.md) - Get source statuses
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
## flux install
|
||||
|
||||
Install the toolkit components
|
||||
Install or upgrade Flux
|
||||
|
||||
### Synopsis
|
||||
|
||||
The install command deploys the toolkit components in the specified namespace.
|
||||
The install command deploys Flux in the specified namespace.
|
||||
If a previous version is installed, then an in-place upgrade will be performed.
|
||||
|
||||
```
|
||||
@@ -17,9 +17,12 @@ flux install [flags]
|
||||
# Install the latest version in the flux-system namespace
|
||||
flux install --version=latest --namespace=flux-system
|
||||
|
||||
# Dry-run install for a specific version and a series of components
|
||||
# Install a specific version and a series of components
|
||||
flux install --dry-run --version=v0.0.7 --components="source-controller,kustomize-controller"
|
||||
|
||||
# Install Flux onto tainted Kubernetes nodes
|
||||
flux install --toleration-keys=node.kubernetes.io/dedicated-to-flux
|
||||
|
||||
# Dry-run install with manifests preview
|
||||
flux install --dry-run --verbose
|
||||
|
||||
@@ -41,7 +44,8 @@ flux install [flags]
|
||||
--log-level logLevel log level, available options are: (debug, info, error) (default info)
|
||||
--network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
|
||||
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd")
|
||||
-v, --version string toolkit version (default "latest")
|
||||
--toleration-keys strings list of toleration keys used to schedule the components pods onto nodes with matching taints
|
||||
-v, --version string toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
|
||||
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
|
||||
```
|
||||
|
||||
|
||||
56
docs/cmd/flux_logs.md
Normal file
56
docs/cmd/flux_logs.md
Normal file
@@ -0,0 +1,56 @@
|
||||
## flux logs
|
||||
|
||||
Display formatted logs for Flux components
|
||||
|
||||
### Synopsis
|
||||
|
||||
The logs command displays formatted logs from various Flux components.
|
||||
|
||||
```
|
||||
flux logs [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
# Print the reconciliation logs of all Flux custom resources in your cluster
|
||||
flux logs --all-namespaces
|
||||
|
||||
# Stream logs for a particular log level
|
||||
flux logs --follow --level=error --all-namespaces
|
||||
|
||||
# Filter logs by kind, name and namespace
|
||||
flux logs --kind=Kustomization --name=podinfo --namespace=default
|
||||
|
||||
# Print logs when Flux is installed in a different namespace than flux-system
|
||||
flux logs --flux-namespace=my-namespace
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-A, --all-namespaces displays logs for objects across all namespaces
|
||||
--flux-namespace string the namespace where the Flux components are running (default "flux-system")
|
||||
-f, --follow specifies if the logs should be streamed
|
||||
-h, --help help for logs
|
||||
--kind string displays errors of a particular toolkit kind e.g GitRepository
|
||||
--level logLevel log level, available options are: (debug, info, error)
|
||||
--name string specifies the name of the object logs to be displayed
|
||||
--tail int lines of recent log file to display (default -1)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--context string kubernetes context to use
|
||||
--kubeconfig string path to the kubeconfig file (default "~/.kube/config")
|
||||
-n, --namespace string the namespace scope for this operation (default "flux-system")
|
||||
--timeout duration timeout for this operation (default 5m0s)
|
||||
--verbose print generated objects
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [flux](flux.md) - Command line utility for assembling Kubernetes CD pipelines
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
## flux uninstall
|
||||
|
||||
Uninstall the toolkit components
|
||||
Uninstall Flux and its custom resource definitions
|
||||
|
||||
### Synopsis
|
||||
|
||||
The uninstall command removes the namespace, cluster roles, cluster role bindings and CRDs from the cluster.
|
||||
The uninstall command removes the Flux components and the toolkit.fluxcd.io resources from the cluster.
|
||||
|
||||
```
|
||||
flux uninstall [flags]
|
||||
@@ -13,22 +13,21 @@ flux uninstall [flags]
|
||||
### Examples
|
||||
|
||||
```
|
||||
# Dry-run uninstall of all components
|
||||
flux uninstall --dry-run --namespace=flux-system
|
||||
# Uninstall Flux components, its custom resources and namespace
|
||||
flux uninstall --namespace=flux-system
|
||||
|
||||
# Uninstall all components and delete custom resource definitions
|
||||
flux uninstall --resources --crds --namespace=flux-system
|
||||
# Uninstall Flux but keep the namespace
|
||||
flux uninstall --namespace=infra --keep-namespace=true
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--crds removes all CRDs previously installed
|
||||
--dry-run only print the object that would be deleted
|
||||
-h, --help help for uninstall
|
||||
--resources removes custom resources such as Kustomizations, GitRepositories and HelmRepositories (default true)
|
||||
-s, --silent delete components without asking for confirmation
|
||||
--dry-run only print the objects that would be deleted
|
||||
-h, --help help for uninstall
|
||||
--keep-namespace skip namespace deletion
|
||||
-s, --silent delete components without asking for confirmation
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -20,6 +20,7 @@ Features:
|
||||
- Runs Helm install/upgrade in a specific order, taking into account the depends-on relationship defined in a set of `HelmRelease` objects
|
||||
- Prunes Helm releases removed from cluster (garbage collection)
|
||||
- Reports Helm releases statuses (alerting provided by [notification-controller](../notification/controller.md))
|
||||
- Built-in Kustomize compatible Helm post renderer, providing support for strategic merge, JSON 6902 and images patches
|
||||
|
||||
Links:
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ repository when new container images are available.
|
||||
- The image-automation-controller updates YAML files based on the latest images scanned, and commits
|
||||
the changes to a given Git repository.
|
||||
|
||||

|
||||
|
||||
Links:
|
||||
|
||||
- Source code [fluxcd/image-reflector-controller](https://github.com/fluxcd/image-reflector-controller)
|
||||
|
||||
@@ -28,7 +28,7 @@ is produced.
|
||||
All sources are specified as Custom Resources in a Kubernetes cluster, examples
|
||||
of sources are `GitRepository`, `HelmRepository` and `Bucket` resources.
|
||||
|
||||
For more information, take a look at [the source controller documentation](../components/source/source.md).
|
||||
For more information, take a look at [the source controller documentation](../components/source/controller.md).
|
||||
|
||||
## Reconciliation
|
||||
|
||||
|
||||
42
docs/dev-guides/debugging.md
Normal file
42
docs/dev-guides/debugging.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Advanced debugging
|
||||
|
||||
This guide covers more advanced debugging topics such as collecting
|
||||
runtime profiling data from GitOps Toolkit components.
|
||||
|
||||
As a user, this page normally should be a last resort, but you may
|
||||
be asked by a maintainer to share a [collected profile](#collecting-a-profile)
|
||||
to debug e.g. performance issues.
|
||||
|
||||
## Pprof
|
||||
|
||||
The [GitOps Toolkit components](../components/index.md) serve [`pprof`](https://golang.org/pkg/net/http/pprof/)
|
||||
runtime profiling data on their metrics HTTP server (default `:8080`).
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Endpoint | Path |
|
||||
|-------------|------------------------|
|
||||
| Index | `/debug/pprof/` |
|
||||
| CPU profile | `/debug/pprof/profile` |
|
||||
| Symbol | `/debug/pprof/symbol` |
|
||||
| Trace | `/debug/pprof/trace` |
|
||||
|
||||
### Collecting a profile
|
||||
|
||||
To collect a profile, port-forward to the component's metrics endpoint
|
||||
and collect the data from the [endpoint](#endpoints) of choice:
|
||||
|
||||
```console
|
||||
$ kubectl port-forward -n <namespace> deploy/<component> 8080
|
||||
$ curl -Sk -v http://localhost:8080/debug/pprof/heap > heap.out
|
||||
```
|
||||
|
||||
The collected profile [can be analyzed using `go`](https://blog.golang.org/pprof),
|
||||
or shared with one of the maintainers.
|
||||
|
||||
## Resource usage
|
||||
|
||||
As `kubectl top` gives a limited (and at times inaccurate) overview of
|
||||
resource usage, it is often better to make use of the Grafana metrics
|
||||
to gather insights. See [monitoring](../guides/monitoring.md) for a
|
||||
guide on how to visualize this data with a Grafana dashboard.
|
||||
@@ -150,6 +150,23 @@ The kustomize-controller creates `kustomization.yaml` files similar to:
|
||||
cd ./deploy/prod && kustomize create --autodetect --recursive
|
||||
```
|
||||
|
||||
### What is the behavior of Kustomize used by Flux
|
||||
|
||||
We referred to the Kustomization CLI flags here, so that you can replicate the same behavior using the CLI.
|
||||
The behavior of Kustomize used by the controller is currently configured as following:
|
||||
|
||||
- `--allow_id_changes` is set to false, so it does not change any resource IDs.
|
||||
- `--enable_kyaml` is disabled by default, so it currently used `k8sdeps` to process YAMLs.
|
||||
- `--enable_alpha_plugins` is disabled by default, so it uses only the built-in plugins.
|
||||
- `--load_restrictor` is set to `LoadRestrictionsNone`, so it allows loading files outside the dir containing `kustomization.yaml`.
|
||||
- `--reorder` resources is done in the `legacy` mode, so the output will have namespaces and cluster roles/role bindings first, CRDs before CRs, and webhooks last.
|
||||
|
||||
!!! hint "`kustomization.yaml` validation"
|
||||
To validate changes before committing and/or merging, [a validation
|
||||
utility script is available](https://github.com/fluxcd/flux2-kustomize-helm-example/blob/main/scripts/validate.sh),
|
||||
it runs `kustomize` locally or in CI with the same set of flags as
|
||||
the controller and validates the output using `kubeval`.
|
||||
|
||||
## Helm questions
|
||||
|
||||
### How to debug "not ready" errors?
|
||||
@@ -181,95 +198,76 @@ NAMESPACE NAME READY MESSAGE
|
||||
default default-podinfo False no chart version found for podinfo-9.0.0
|
||||
```
|
||||
|
||||
### Can I use Flux HelmReleases without GitOps?
|
||||
|
||||
Yes, you can install the Flux components directly on a cluster
|
||||
and manage Helm releases with `kubectl`.
|
||||
|
||||
Install the controllers needed for Helm operations with `flux`:
|
||||
|
||||
```sh
|
||||
flux install \
|
||||
--namespace=flux-system \
|
||||
--network-policy=false \
|
||||
--components=source-controller,helm-controller
|
||||
```
|
||||
|
||||
Create a Helm release with `kubectl`:
|
||||
|
||||
```sh
|
||||
cat << EOF | kubectl apply -f -
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bitnami
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 30m
|
||||
url: https://charts.bitnami.com/bitnami
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2beta1
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: metrics-server
|
||||
namespace: kube-system
|
||||
spec:
|
||||
interval: 60m
|
||||
releaseName: metrics-server
|
||||
chart:
|
||||
spec:
|
||||
chart: metrics-server
|
||||
version: "^5.x"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bitnami
|
||||
namespace: flux-system
|
||||
values:
|
||||
apiService:
|
||||
create: true
|
||||
EOF
|
||||
```
|
||||
|
||||
Based on the above definition, Flux will upgrade the release automatically
|
||||
when Bitnami publishes a new version of the metrics-server chart.
|
||||
|
||||
## Flux v1 vs v2 questions
|
||||
|
||||
### What does Flux v2 mean for Flux?
|
||||
### What are the differences between v1 and v2?
|
||||
|
||||
Flux v1 is a monolithic do-it-all operator; Flux v2 separates the functionalities into specialized controllers, collectively called the GitOps Toolkit.
|
||||
Flux v1 is a monolithic do-it-all operator;
|
||||
Flux v2 separates the functionalities into specialized controllers, collectively called the GitOps Toolkit.
|
||||
|
||||
You can install and operate Flux v2 simply using the `flux` command. You can easily pick and choose the functionality you need and extend it to serve your own purposes.
|
||||
You can find a detailed comparison of Flux v1 and v2 features in the [migration FAQ](../guides/faq-migration.md).
|
||||
|
||||
The timeline we are looking at right now is:
|
||||
### How can I migrate from v1 to v2?
|
||||
|
||||
1. Put Flux v1 into maintenance mode (no new features being added; bugfixes and CVEs patched only).
|
||||
1. Continue work on the [Flux v2 roadmap](https://toolkit.fluxcd.io/roadmap/).
|
||||
1. We will provide transition guides for specific user groups, e.g. users of Flux v1 in read-only mode, or of Helm Operator v1, etc. once the functionality is integrated into Flux v2 and it's deemed "ready".
|
||||
1. Once the use-cases of Flux v1 are covered, we will continue supporting Flux v1 for 6 months. This will be the transition period before it's considered unsupported.
|
||||
The Flux community has created guides and example repositories
|
||||
to help you migrate to Flux v2:
|
||||
|
||||
### Why did you rewrite Flux?
|
||||
|
||||
Flux v2 implements its functionality in individual controllers, which allowed us to address long-standing feature requests much more easily.
|
||||
|
||||
By basing these controllers on modern Kubernetes tooling (`controller-runtime` libraries), they can be dynamically configured with Kubernetes custom resources either by cluster admins or by other automated tools -- and you get greatly increased observability.
|
||||
|
||||
This gave us the opportunity to build Flux v2 with the top Flux v1 feature requests in mind:
|
||||
|
||||
- Supporting multiple source Git repositories
|
||||
- Operational insight through health checks, events and alerts
|
||||
- Multi-tenancy capabilities, like applying each source repository with its own set of permissions
|
||||
|
||||
On top of that, testing the individual components and understanding the codebase becomes a lot easier.
|
||||
|
||||
### What are significant new differences between Flux v1 and Flux v2?
|
||||
|
||||
#### Reconciliation
|
||||
|
||||
Flux v1 | Flux v2
|
||||
---------------------------------- | ----------------------------------
|
||||
Limited to a single Git repository | Multiple Git repositories
|
||||
Declarative config via arguments in the Flux deployment | `GitRepository` custom resource, which produces an artifact which can be reconciled by other controllers
|
||||
Follow `HEAD` of Git branches | Supports Git branches, pinning on commits and tags, follow SemVer tag ranges
|
||||
Suspending of reconciliation by downscaling Flux deployment | Reconciliation can be paused per resource by suspending the `GitRepository`
|
||||
Credentials config via Arguments and/or Secret volume mounts in the Flux pod | Credentials config per `GitRepository` resource: SSH private key, HTTP/S username/password/token, OpenPGP public keys
|
||||
|
||||
#### `kustomize` support
|
||||
|
||||
Flux v1 | Flux v2
|
||||
---------------------------------- | ----------------------------------
|
||||
Declarative config through `.flux.yaml` files in the Git repository | Declarative config through a `Kustomization` custom resource, consuming the artifact from the GitRepository
|
||||
Manifests are generated via shell exec and then reconciled by `fluxd` | Generation, server-side validation, and reconciliation is handled by a specialised `kustomize-controller`
|
||||
Reconciliation using the service account of the Flux deployment | Support for service account impersonation
|
||||
Garbage collection needs cluster role binding for Flux to query the Kubernetes discovery API | Garbage collection needs no cluster role binding or access to Kubernetes discovery API
|
||||
Support for custom commands and generators executed by fluxd in a POSIX shell | No support for custom commands
|
||||
|
||||
#### Helm integration
|
||||
|
||||
Flux v1 | Flux v2
|
||||
---------------------------------- | ----------------------------------
|
||||
Declarative config in a single Helm custom resource | Declarative config through `HelmRepository`, `GitRepository`, `Bucket`, `HelmChart` and `HelmRelease` custom resources
|
||||
Chart synchronisation embedded in the operator | Extensive release configuration options, and a reconciliation interval per source
|
||||
Support for fixed SemVer versions from Helm repositories | Support for SemVer ranges for `HelmChart` resources
|
||||
Git repository synchronisation on a global interval | Planned support for charts from GitRepository sources
|
||||
Limited observability via the status object of the HelmRelease resource | Better observability via the HelmRelease status object, Kubernetes events, and notifications
|
||||
Resource heavy, relatively slow | Better performance
|
||||
Chart changes from Git sources are determined from Git metadata | Chart changes must be accompanied by a version bump in `Chart.yaml` to produce a new artifact
|
||||
|
||||
#### Notifications, webhooks, observability
|
||||
|
||||
Flux v1 | Flux v2
|
||||
---------------------------------- | ----------------------------------
|
||||
Emits "custom Flux events" to a webhook endpoint | Emits Kubernetes events for included custom resources
|
||||
RPC endpoint can be configured to a 3rd party solution like FluxCloud to be forwarded as notifications to e.g. Slack | Flux v2 components can be configured to POST the events to a `notification-controller` endpoint. Selective forwarding of POSTed events as notifications using `Provider` and `Alert` custom resources.
|
||||
Webhook receiver is a side-project | Webhook receiver, handling a wide range of platforms, is included
|
||||
Unstructured logging | Structured logging for all components
|
||||
Custom Prometheus metrics | Generic / common `controller-runtime` Prometheus metrics
|
||||
|
||||
### How can I get involved?
|
||||
|
||||
There are a variety of ways and we look forward to having you on board building the future of GitOps together:
|
||||
|
||||
- [Discuss the direction](https://github.com/fluxcd/flux2/discussions) of Flux v2 with us
|
||||
- Join us in #flux-dev on the [CNCF Slack](https://slack.cncf.io)
|
||||
- Check out our [contributor docs](https://toolkit.fluxcd.io/contributing/)
|
||||
- Take a look at the [roadmap for Flux v2](https://toolkit.fluxcd.io/roadmap/)
|
||||
|
||||
### Are there any breaking changes?
|
||||
|
||||
- In Flux v1 Kustomize support was implemented through `.flux.yaml` files in the Git repository. As indicated in the comparison table above, while this approach worked, we found it to be error-prone and hard to debug. The new [Kustomization CR](https://github.com/fluxcd/kustomize-controller/blob/master/docs/spec/v1alpha1/kustomization.md) should make troubleshooting much easier. Unfortunately we needed to drop the support for custom commands as running arbitrary shell scripts in-cluster poses serious security concerns.
|
||||
- Helm users: we redesigned the `HelmRelease` API and the automation will work quite differently, so upgrading to `HelmRelease` v2 will require a little work from you, but you will gain more flexibility, better observability and performance.
|
||||
|
||||
### Is the GitOps Toolkit related to the GitOps Engine?
|
||||
|
||||
In an announcement in August 2019, the expectation was set that the Flux project would integrate the GitOps Engine, then being factored out of ArgoCD. Since the result would be backward-incompatible, it would require a major version bump: Flux v2.
|
||||
|
||||
After experimentation and considerable thought, we (the maintainers) have found a path to Flux v2 that we think better serves our vision of GitOps: the GitOps Toolkit. In consequence, we do not now plan to integrate GitOps Engine into Flux.
|
||||
- [Migrate from Flux v1](https://toolkit.fluxcd.io/guides/flux-v1-migration/)
|
||||
- [Migrate from `.flux.yaml` and kustomize](https://toolkit.fluxcd.io/guides/flux-v1-migration/#flux-with-kustomize)
|
||||
- [Migrate from Flux v1 automated container image updates](https://toolkit.fluxcd.io/guides/flux-v1-automation-migration/)
|
||||
- [How to manage multi-tenant clusters with Flux v2](https://github.com/fluxcd/flux2-multi-tenancy)
|
||||
- [Migrate from Helm Operator to Flux v2](https://toolkit.fluxcd.io/guides/helm-operator-migration/)
|
||||
- [How to structure your HelmReleases](https://github.com/fluxcd/flux2-kustomize-helm-example)
|
||||
|
||||
92
docs/guides/faq-migration.md
Normal file
92
docs/guides/faq-migration.md
Normal file
@@ -0,0 +1,92 @@
|
||||
## Flux v1 vs v2 questions
|
||||
|
||||
### What does Flux v2 mean for Flux?
|
||||
|
||||
Flux v1 is a monolithic do-it-all operator; Flux v2 separates the functionalities into specialized controllers, collectively called the GitOps Toolkit.
|
||||
|
||||
You can install and operate Flux v2 simply using the `flux` command. You can easily pick and choose the functionality you need and extend it to serve your own purposes.
|
||||
|
||||
The timeline we are looking at right now is:
|
||||
|
||||
1. Put Flux v1 into maintenance mode (no new features being added; bugfixes and CVEs patched only).
|
||||
1. Continue work on the [Flux v2 roadmap](https://toolkit.fluxcd.io/roadmap/).
|
||||
1. We will provide transition guides for specific user groups, e.g. users of Flux v1 in read-only mode, or of Helm Operator v1, etc. once the functionality is integrated into Flux v2 and it's deemed "ready".
|
||||
1. Once the use-cases of Flux v1 are covered, we will continue supporting Flux v1 for 6 months. This will be the transition period before it's considered unsupported.
|
||||
|
||||
### Why did you rewrite Flux?
|
||||
|
||||
Flux v2 implements its functionality in individual controllers, which allowed us to address long-standing feature requests much more easily.
|
||||
|
||||
By basing these controllers on modern Kubernetes tooling (`controller-runtime` libraries), they can be dynamically configured with Kubernetes custom resources either by cluster admins or by other automated tools -- and you get greatly increased observability.
|
||||
|
||||
This gave us the opportunity to build Flux v2 with the top Flux v1 feature requests in mind:
|
||||
|
||||
- Supporting multiple source Git repositories
|
||||
- Operational insight through health checks, events and alerts
|
||||
- Multi-tenancy capabilities, like applying each source repository with its own set of permissions
|
||||
|
||||
On top of that, testing the individual components and understanding the codebase becomes a lot easier.
|
||||
|
||||
### What are significant new differences between Flux v1 and Flux v2?
|
||||
|
||||
#### Reconciliation
|
||||
|
||||
Flux v1 | Flux v2
|
||||
---------------------------------- | ----------------------------------
|
||||
Limited to a single Git repository | Multiple Git repositories
|
||||
Declarative config via arguments in the Flux deployment | `GitRepository` custom resource, which produces an artifact which can be reconciled by other controllers
|
||||
Follow `HEAD` of Git branches | Supports Git branches, pinning on commits and tags, follow SemVer tag ranges
|
||||
Suspending of reconciliation by downscaling Flux deployment | Reconciliation can be paused per resource by suspending the `GitRepository`
|
||||
Credentials config via Arguments and/or Secret volume mounts in the Flux pod | Credentials config per `GitRepository` resource: SSH private key, HTTP/S username/password/token, OpenPGP public keys
|
||||
|
||||
#### `kustomize` support
|
||||
|
||||
Flux v1 | Flux v2
|
||||
---------------------------------- | ----------------------------------
|
||||
Declarative config through `.flux.yaml` files in the Git repository | Declarative config through a `Kustomization` custom resource, consuming the artifact from the GitRepository
|
||||
Manifests are generated via shell exec and then reconciled by `fluxd` | Generation, server-side validation, and reconciliation is handled by a specialised `kustomize-controller`
|
||||
Reconciliation using the service account of the Flux deployment | Support for service account impersonation
|
||||
Garbage collection needs cluster role binding for Flux to query the Kubernetes discovery API | Garbage collection needs no cluster role binding or access to Kubernetes discovery API
|
||||
Support for custom commands and generators executed by fluxd in a POSIX shell | No support for custom commands
|
||||
|
||||
#### Helm integration
|
||||
|
||||
Flux v1 | Flux v2
|
||||
---------------------------------- | ----------------------------------
|
||||
Declarative config in a single Helm custom resource | Declarative config through `HelmRepository`, `GitRepository`, `Bucket`, `HelmChart` and `HelmRelease` custom resources
|
||||
Chart synchronisation embedded in the operator | Extensive release configuration options, and a reconciliation interval per source
|
||||
Support for fixed SemVer versions from Helm repositories | Support for SemVer ranges for `HelmChart` resources
|
||||
Git repository synchronisation on a global interval | Planned support for charts from GitRepository sources
|
||||
Limited observability via the status object of the HelmRelease resource | Better observability via the HelmRelease status object, Kubernetes events, and notifications
|
||||
Resource heavy, relatively slow | Better performance
|
||||
Chart changes from Git sources are determined from Git metadata | Chart changes must be accompanied by a version bump in `Chart.yaml` to produce a new artifact
|
||||
|
||||
#### Notifications, webhooks, observability
|
||||
|
||||
Flux v1 | Flux v2
|
||||
---------------------------------- | ----------------------------------
|
||||
Emits "custom Flux events" to a webhook endpoint | Emits Kubernetes events for included custom resources
|
||||
RPC endpoint can be configured to a 3rd party solution like FluxCloud to be forwarded as notifications to e.g. Slack | Flux v2 components can be configured to POST the events to a `notification-controller` endpoint. Selective forwarding of POSTed events as notifications using `Provider` and `Alert` custom resources.
|
||||
Webhook receiver is a side-project | Webhook receiver, handling a wide range of platforms, is included
|
||||
Unstructured logging | Structured logging for all components
|
||||
Custom Prometheus metrics | Generic / common `controller-runtime` Prometheus metrics
|
||||
|
||||
### Are there any breaking changes?
|
||||
|
||||
- In Flux v1 Kustomize support was implemented through `.flux.yaml` files in the Git repository. As indicated in the comparison table above, while this approach worked, we found it to be error-prone and hard to debug. The new [Kustomization CR](https://github.com/fluxcd/kustomize-controller/blob/master/docs/spec/v1alpha1/kustomization.md) should make troubleshooting much easier. Unfortunately we needed to drop the support for custom commands as running arbitrary shell scripts in-cluster poses serious security concerns.
|
||||
- Helm users: we redesigned the `HelmRelease` API and the automation will work quite differently, so upgrading to `HelmRelease` v2 will require a little work from you, but you will gain more flexibility, better observability and performance.
|
||||
|
||||
### Is the GitOps Toolkit related to the GitOps Engine?
|
||||
|
||||
In an announcement in August 2019, the expectation was set that the Flux project would integrate the GitOps Engine, then being factored out of ArgoCD. Since the result would be backward-incompatible, it would require a major version bump: Flux v2.
|
||||
|
||||
After experimentation and considerable thought, we (the maintainers) have found a path to Flux v2 that we think better serves our vision of GitOps: the GitOps Toolkit. In consequence, we do not now plan to integrate GitOps Engine into Flux.
|
||||
|
||||
### How can I get involved?
|
||||
|
||||
There are a variety of ways and we look forward to having you on board building the future of GitOps together:
|
||||
|
||||
- [Discuss the direction](https://github.com/fluxcd/flux2/discussions) of Flux v2 with us
|
||||
- Join us in #flux-dev on the [CNCF Slack](https://slack.cncf.io)
|
||||
- Check out our [contributor docs](https://toolkit.fluxcd.io/contributing/)
|
||||
- Take a look at the [roadmap for Flux v2](https://toolkit.fluxcd.io/roadmap/)
|
||||
771
docs/guides/flux-v1-automation-migration.md
Normal file
771
docs/guides/flux-v1-automation-migration.md
Normal file
@@ -0,0 +1,771 @@
|
||||
<!-- -*- fill-column: 100 -*- -->
|
||||
# Migrating image update automation to Flux v2
|
||||
|
||||
"Image Update Automation" is a process in which Flux makes commits to your Git repository when it
|
||||
detects that there is a new image to be used in a workload (e.g., a Deployment). In Flux v2 this
|
||||
works quite differently to how it worked in Flux v1. This guide explains the differences and how to
|
||||
port your cluster configuration from v1 to v2. There is also a [tutorial for using image update
|
||||
automation with a new cluster][image-update-tute].
|
||||
|
||||
## Overview of changes between v1 and v2
|
||||
|
||||
In Flux v1, image update automation (from here, just "automation") was built into the Flux daemon,
|
||||
which scanned everything it found in the cluster and updated the Git repository it was syncing.
|
||||
|
||||
In Flux v2,
|
||||
|
||||
- automation is controlled with custom resources, not annotations
|
||||
- ordering images by build time is not supported (there is [a section
|
||||
below](#how-to-migrate-annotations-to-image-policies) explaining what to do instead)
|
||||
- the fields to update in files are marked explicitly, rather than inferred from annotations.
|
||||
|
||||
#### Automation is now controlled by custom resources
|
||||
|
||||
Flux v2 breaks down the functions in Flux v1's daemon into controllers, with each having a specific
|
||||
area of concern. Automation is now done by two controllers: one which scans image repositories to
|
||||
find the latest images, and one which uses that information to commit changes to git
|
||||
repositories. These are in turn separate to the syncing controllers.
|
||||
|
||||
This means that automation in Flux v2 is governed by custom resources. In Flux v1 the daemon scanned
|
||||
everything, and looked at annotations on the resources to determine what to update. Automation in v2
|
||||
is more explicit than in v1 -- you have to mention exactly which images you want to be scanned, and
|
||||
which fields you want to be updated.
|
||||
|
||||
A consequence of using custom resources is that with Flux v2 you can have an arbitrary number of
|
||||
automations, targeting different Git repositories if you wish, and updating different sets of
|
||||
images. If you run a multitenant cluster, the tenants can define automation in their own namespaces,
|
||||
for their own Git repositories.
|
||||
|
||||
#### Selecting an image is more flexible
|
||||
|
||||
The ways in which you choose to select an image have changed. In Flux v1, you generally supply a
|
||||
filter pattern, and the latest image is the image with the most recent build time out of those
|
||||
filtered. In Flux v2, you choose an ordering, and separately specify a filter for the tags to
|
||||
consider. These are dealt with in detail below.
|
||||
|
||||
Selecting an image by build time is no longer supported. This is the implicit default in Flux v1. In
|
||||
Flux v2, you will need to tag images so that they sort in the order you would like -- [see
|
||||
below](#how-to-use-sortable-image-tags) for how to do this conveniently.
|
||||
|
||||
#### Fields to update are explicitly marked
|
||||
|
||||
Lastly, in Flux v2 the fields to update in files are marked explicitly. In Flux v1 they are inferred
|
||||
from the type of the resource, along with the annotations given. The approach in Flux v1 was limited
|
||||
to the types that had been programmed in, whereas Flux v2 can update any Kubernetes object (and some
|
||||
files that aren't Kubernetes objects, like `kustomization.yaml`).
|
||||
|
||||
## Preparing for migration
|
||||
|
||||
It is best to complete migration of your system to _Flux v2 syncing_ first, using the [Flux v1
|
||||
migration guide][flux-v1-migration]. This will remove Flux v1 from the system, along with its image
|
||||
automation. You can then reintroduce automation with Flux v2 by following the instructions in this
|
||||
guide.
|
||||
|
||||
It is safe to leave the annotations for Flux v1 in files while you reintroduce automation, because
|
||||
Flux v2 will ignore them.
|
||||
|
||||
To migrate to Flux v2 automation, you will need to do three things:
|
||||
|
||||
- make sure you are running the automation controllers; then,
|
||||
- declare the automation with an `ImageUpdateAutomation` object; and,
|
||||
- migrate each manifest by translate Flux v1 annotations to Flux v2 `ImageRepository` and
|
||||
`ImagePolicy` objects, and putting update markers in the manifest file.
|
||||
|
||||
### Where to keep `ImageRepository`, `ImagePolicy` and `ImageUpdateAutomation` manifests
|
||||
|
||||
This guide assumes you want to manage automation itself via Flux. In the following sections,
|
||||
manifests for the objects controlling automation are saved in files, committed to Git, and applied
|
||||
in the cluster with Flux.
|
||||
|
||||
A Flux v2 installation will typically have a Git repository structured like this:
|
||||
|
||||
```
|
||||
<...>/flux-system/
|
||||
gotk-components.yaml
|
||||
gotk-sync.yaml
|
||||
<...>/app/
|
||||
# deployments etc.
|
||||
```
|
||||
|
||||
The `<...>` is the path to a particular cluster's definitions -- this may be simply `.`, or
|
||||
something like `clusters/my-cluster`. To get the files in the right place, set a variable for this
|
||||
path:
|
||||
|
||||
```bash
|
||||
$ CLUSTER_PATH=<...> # e.g., "." or "clusters/my-cluster", or ...
|
||||
$ AUTO_PATH=$CLUSTER_PATH/automation
|
||||
$ mkdir ./$AUTO_PATH
|
||||
```
|
||||
|
||||
The file `$CLUSTER_PATH/flux-system/gotk-components.yaml` has definitions of all the Flux v2
|
||||
controllers and custom resource definitions. The file `gotk-sync.yaml` defines a `GitRepository` and
|
||||
a `Kustomization` which will sync manifests under `$CLUSTER_PATH/`.
|
||||
|
||||
To these will be added definitions for automation objects. This guide puts manifest files for
|
||||
automation in `$CLUSTER_PATH/automation/`, but there is no particular structure required
|
||||
by Flux. The automation objects do not have to be in the same namespace as the objects to be
|
||||
updated.
|
||||
|
||||
#### Migration on a branch
|
||||
|
||||
This guide assumes you will commit changes to the branch that is synced by Flux, as this is the
|
||||
simplest way to understand.
|
||||
|
||||
It may be less disruptive to put migration changes on a branch, then merging when you have completed
|
||||
the migration. You would need to either change the `GitRepository` to point at the migration branch,
|
||||
or have separate `GitRepository` and `Kustomization` objects for the migrated parts of your Git
|
||||
repository. The main thing to avoid is syncing the same objects in two different places; e.g., avoid
|
||||
having Kustomizations that sync both the unmigrated and migrated application configuration.
|
||||
|
||||
### Installing the command-line tool `flux`
|
||||
|
||||
The command-line tool `flux` will be used below; see [these instructions][install-cli] for how to
|
||||
install it.
|
||||
|
||||
## Running the automation controllers
|
||||
|
||||
The first thing to do is to deploy the automation controllers to your cluster. The best way to
|
||||
proceed will depend on the approach you took when following the [Flux read-only migration
|
||||
guide][flux-v1-migration].
|
||||
|
||||
- If you used `flux bootstrap` to create a new Git repository, then ported your cluster
|
||||
configuration to that repository, use [After `flux bootstrap`](#after-flux-bootstrap);
|
||||
- If you used `flux install` to install the controllers directly, use [After migrating Flux v1 in
|
||||
place](#after-migrating-flux-v1-in-place);
|
||||
- If you used `flux install` and exported the configuration to a file, use [After committing Flux
|
||||
v2 configuration to Git](#after-committing-a-flux-v2-configuration-to-git).
|
||||
|
||||
### After `flux bootstrap`
|
||||
|
||||
When starting from scratch, you are likely to have used `flux bootstrap`. Rerun the command, and
|
||||
include the image automation controllers in your starting configuration with the flag
|
||||
`--components-extra`, [as shown in the installation guide][flux-bootstrap].
|
||||
|
||||
This will commit changes to your Git repository and sync them in the cluster.
|
||||
|
||||
```bash
|
||||
flux check --components-extra=image-reflector-controller,image-automation-controller
|
||||
```
|
||||
|
||||
Now jump to the section [Migrating each manifest to Flux v2](#migrating-each-manifest-to-flux-v2).
|
||||
|
||||
### After migrating Flux v1 in place
|
||||
|
||||
If you followed the [Flux v1 migration guide][flux-v1-migration], you will already be running some
|
||||
Flux v2 controllers. The automation controllers are currently considered an optional extra to those,
|
||||
but are installed and run in much the same way. You may or may not have committed the Flux v2
|
||||
configuration to your Git repository. If you did, go to the section [After committing Flux v2
|
||||
configuration to Git](#after-committing-flux-v2-configuration-to-git).
|
||||
|
||||
If _not_, you will be installing directly to the cluster:
|
||||
|
||||
```bash
|
||||
$ flux install --components-extra=image-reflector-controller,image-automation-controller
|
||||
```
|
||||
|
||||
It is safe to repeat the installation command, or to run it after using `flux bootstrap`, so long as
|
||||
you repeat any arguments you supplied the first time.
|
||||
|
||||
Now jump ahead to [Migrating each manifest to Flux v2](#migrating-each-manifest-to-flux-v2).
|
||||
|
||||
#### After committing a Flux v2 configuration to Git
|
||||
|
||||
If you added the Flux v2 configuration to your git repository, assuming it's in the file
|
||||
`$CLUSTER_PATH/flux-system/gotk-components.yaml` as used in the guide, use `flux install` and write
|
||||
it back to that file:
|
||||
|
||||
```bash
|
||||
$ flux install \
|
||||
--components-extra=image-reflector-controller,image-automation-controller \
|
||||
--export > "$CLUSTER_PATH/flux-system/gotk-components.yaml"
|
||||
```
|
||||
|
||||
Commit changes to the `$CLUSTER_PATH/flux-system/gotk-components.yaml` file and sync the cluster:
|
||||
|
||||
```bash
|
||||
$ git add $CLUSTER_PATH/flux-system/gotk-components.yaml
|
||||
$ git commit -s -m "Add image automation controllers to Flux config"
|
||||
$ git push
|
||||
$ flux reconcile kustomization --with-source flux-system
|
||||
```
|
||||
|
||||
## Controlling automation with an `ImageUpdateAutomation` object
|
||||
|
||||
In Flux v1, automation was run by default. With Flux v2, you have to explicitly tell the controller
|
||||
which Git repository to update and how to do so. These are defined in an `ImageUpdateAutomation`
|
||||
object; but first, you need a `GitRepository` with write access, for the automation to use.
|
||||
|
||||
If you followed the [Flux v1 read-only migration guide][flux-v1-migration], you will have a
|
||||
`GitRepository` defined in the namespace `flux-system`, for syncing to use. This `GitRepository`
|
||||
will have _read_ access to the Git repository by default, and automation needs _write_ access to
|
||||
push commits.
|
||||
|
||||
To give it write access, you can replace the secret it refers to. How to do this will depend on what
|
||||
kind of authentication you used to install Flux v2.
|
||||
|
||||
### Replacing the Git credentials secret
|
||||
|
||||
The secret with Git credentials will be named in the `.spec.secretRef.name` field of the
|
||||
`GitRepository` object. Say your `GitRepository` is in the _namespace_ `flux-system` and _named_
|
||||
`flux-system` (these are the defaults if you used `flux bootstrap`); you can retrieve the secret
|
||||
name and Git URL with:
|
||||
|
||||
```bash
|
||||
$ FLUX_NS=flux-system
|
||||
$ GIT_NAME=flux-system
|
||||
$ SECRET_NAME=$(kubectl -n $FLUX_NS get gitrepository $GIT_NAME -o jsonpath={.spec.secretRef.name})
|
||||
$ GIT_URL=$(kubectl -n $FLUX_NS get gitrepository $GIT_NAME -o jsonpath='{.spec.url}')
|
||||
$ echo $SECRET_NAME $GIT_URL # make sure they have values
|
||||
```
|
||||
|
||||
If you're not sure which kind of credentials you're using, look at the secret:
|
||||
|
||||
```bash
|
||||
$ kubectl -n $FLUX_NS describe secret $SECRET_NAME
|
||||
```
|
||||
|
||||
An entry at `.data.identity` indicates that you are using an SSH key (the [first
|
||||
section](#replacing-an-ssh-key-secret) below); an entry at `.data.username` indicates you are using
|
||||
a username and password or token (the [second section](#replacing-a-usernamepassword-secret)
|
||||
below).
|
||||
|
||||
#### Replacing an SSH key secret
|
||||
|
||||
When using an SSH (deploy) key, create a new key:
|
||||
|
||||
```bash
|
||||
$ flux create secret git -n $FLUX_NS $SECRET_NAME --url=$GIT_URL
|
||||
```
|
||||
|
||||
You will need to copy the public key that's printed out, and install that as a deploy key for your
|
||||
Git repo **making sure to check the 'All write access' box** (or otherwise give the key write
|
||||
permissions). Remove the old deploy key.
|
||||
|
||||
#### Replacing a username/password secret
|
||||
|
||||
When you're using a username and password to authenticate, you may be able to change the permissions
|
||||
associated with that account.
|
||||
|
||||
If not, you will need to create a new access token (e.g., ["Personal Access Token"][github-pat] in
|
||||
GitHub). In this case, once you have the new token you can replace the secret with the following:
|
||||
|
||||
```bash
|
||||
$ flux create secret git -n $FLUX_NS $SECRET_NAME \
|
||||
--username <username> --password <token> --url $GIT_URL
|
||||
```
|
||||
|
||||
#### Checking the new credentials
|
||||
|
||||
To check if your replaced credentials still work, try syncing the `GitRepository` object:
|
||||
|
||||
```bash
|
||||
$ flux reconcile source git -n $FLUX_NS $GIT_NAME
|
||||
► annotating GitRepository flux-system in flux-system namespace
|
||||
✔ GitRepository annotated
|
||||
◎ waiting for GitRepository reconciliation
|
||||
✔ GitRepository reconciliation completed
|
||||
✔ fetched revision main/d537304e8f5f41f1584ca1e807df5b5752b2577e
|
||||
```
|
||||
|
||||
When this is successful, it tells you the new credentials have at least read access.
|
||||
|
||||
### Making an automation object
|
||||
|
||||
To set automation running, you create an [`ImageUpdateAutomation`][auto-ref] object. Each object
|
||||
will update a Git repository, according to the image policies in the namespace.
|
||||
|
||||
Here is an `ImageUpdateAutomation` manifest for the example (note: you will have to supply your own
|
||||
value for at least the host part of the email address):
|
||||
|
||||
```yaml
|
||||
$ # the environment variables $AUTO_PATH and $GIT_NAME are set above
|
||||
$ FLUXBOT_EMAIL=fluxbot@example.com # supply your own host or address here
|
||||
$ flux create image update my-app-auto \
|
||||
--author-name FluxBot --author-email "$FLUXBOT_EMAIL" \
|
||||
--git-repo-ref $GIT_NAME --branch main \
|
||||
--interval 5m \
|
||||
--export > ./$AUTO_PATH/my-app-auto.yaml
|
||||
$ cat my-app-auto.yaml
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1alpha1
|
||||
kind: ImageUpdateAutomation
|
||||
metadata:
|
||||
name: my-app-auto
|
||||
namespace: flux-system
|
||||
spec:
|
||||
checkout:
|
||||
branch: main
|
||||
gitRepositoryRef:
|
||||
name: flux-system
|
||||
commit:
|
||||
authorEmail: fluxbot@example.com
|
||||
authorName: FluxBot
|
||||
interval: 5m0s
|
||||
```
|
||||
|
||||
#### Commit and check that the automation object works
|
||||
|
||||
Commit the manifeat file and push:
|
||||
|
||||
```bash
|
||||
$ git add ./$AUTO_PATH/my-app-auto.yaml
|
||||
$ git commit -s -m "Add image update automation"
|
||||
$ git push
|
||||
# ...
|
||||
```
|
||||
|
||||
Then sync and check the object status:
|
||||
|
||||
```bash
|
||||
$ flux reconcile kustomization --with-source flux-system
|
||||
► annotating GitRepository flux-system in flux-system namespace
|
||||
✔ GitRepository annotated
|
||||
◎ waiting for GitRepository reconciliation
|
||||
✔ GitRepository reconciliation completed
|
||||
✔ fetched revision main/401dd3b550f82581c7d12bb79ade389089c6422f
|
||||
► annotating Kustomization flux-system in flux-system namespace
|
||||
✔ Kustomization annotated
|
||||
◎ waiting for Kustomization reconciliation
|
||||
✔ Kustomization reconciliation completed
|
||||
✔ reconciled revision main/401dd3b550f82581c7d12bb79ade389089c6422f
|
||||
$ flux get image update
|
||||
NAME READY MESSAGE LAST RUN SUSPENDED
|
||||
my-app-auto True no updates made 2021-02-08T14:53:43Z False
|
||||
```
|
||||
|
||||
Read on to the next section to see how to change each manifest file to work with Flux v2.
|
||||
|
||||
## Migrating each manifest to Flux v2
|
||||
|
||||
In Flux v1, the annotation
|
||||
|
||||
fluxcd.io/automated: "true"
|
||||
|
||||
switches automation on for a manifest (a description of a Kubernetes object). For each manifest that
|
||||
has that annotation, you will need to create custom resources to scan for the latest image, and to
|
||||
replace the annotations with field markers.
|
||||
|
||||
The following sections explain these steps, using this example Deployment manifest which is
|
||||
initially annotated to work with Flux v1:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: my-app
|
||||
namespace: default
|
||||
annotations:
|
||||
fluxcd.io/automated: "true"
|
||||
fluxcd.io/tag.app: semver:^5.0
|
||||
selector:
|
||||
matchLabels:
|
||||
app: podinfo
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: podinfo
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: ghcr.io/stefanprodan/podinfo:5.0.0
|
||||
```
|
||||
|
||||
!!! warning
|
||||
A YAML file may have more than one manifest in it, separated with
|
||||
`---`. Be careful to account for each manifest in a file.
|
||||
|
||||
You may wish to try migrating the automation of just one file or manifest and follow it through to
|
||||
the end of the guide, before returning here to do the remainder.
|
||||
|
||||
### How to migrate annotations to image policies
|
||||
|
||||
For each image repository that is the subject of automation you will need to create an
|
||||
`ImageRepository` object, so that the image repository is scanned for tags. The image repository in
|
||||
the example deployment is `ghcr.io/stefanprodan/podinfo`, which is the image reference minus its
|
||||
tag:
|
||||
|
||||
```yaml
|
||||
$ cat $CLUSTER_PATH/app/my-app.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: my-app
|
||||
namespace: default
|
||||
annotations:
|
||||
fluxcd.io/automated: "true"
|
||||
fluxcd.io/tag.app: semver:^5.0
|
||||
selector:
|
||||
matchLabels:
|
||||
app: podinfo
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: podinfo
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: ghcr.io/stefanprodan/podinfo:5.0.0 # <-- image reference
|
||||
```
|
||||
|
||||
The command-line tool `flux` will help create a manifest for you. Note that the output is redirected
|
||||
to a file under `$AUTO_PATH`, so it can be added to the Git repository and synced to the cluster.
|
||||
|
||||
```bash
|
||||
$ # the environment variable $AUTO_PATH was set earlier
|
||||
$ flux create image repository podinfo-image \
|
||||
--image ghcr.io/stefanprodan/podinfo \
|
||||
--interval 5m \
|
||||
--export > ./$AUTO_PATH/podinfo-image.yaml
|
||||
$ cat ./$AUTO_PATH/podinfo-image.yaml
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1alpha1
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: podinfo-image
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: ghcr.io/stefanprodan/podinfo
|
||||
interval: 5m0s
|
||||
```
|
||||
|
||||
!!! hint
|
||||
If you are using the same image repository in several manifests, you only need one
|
||||
`ImageRepository` object for it.
|
||||
|
||||
##### Using image registry credentials for scanning
|
||||
|
||||
When your image repositories are private, you supply Kubernetes with "image pull secrets" with
|
||||
credentials for accessing the image registry (e.g., DockerHub). The image reflector controller needs
|
||||
the same kind of credentials to scan image repositories.
|
||||
|
||||
There are several ways that image pull secrets can be made available for the image reflector
|
||||
controller. The [image update tutorial][image-update-tute-creds] describes how to create or arrange
|
||||
secrets for scanning to use. Also see later in the tutorial for [instructions specific to some cloud
|
||||
platforms][image-update-tute-clouds].
|
||||
|
||||
##### Committing and checking the ImageRepository
|
||||
|
||||
Add the `ImageRepository` manifest to the Git index and commit it:
|
||||
|
||||
```bash
|
||||
$ git add ./$AUTO_PATH/podinfo-image.yaml
|
||||
$ git commit -s -m "Add image repository object for podinfo"
|
||||
$ git push
|
||||
# ...
|
||||
```
|
||||
|
||||
Now you can sync the new commit, and check that the object is working:
|
||||
|
||||
```bash
|
||||
$ flux reocncile kustomization --with-source flux-system
|
||||
► annotating GitRepository flux-system in flux-system namespace
|
||||
✔ GitRepository annotated
|
||||
◎ waiting for GitRepository reconciliation
|
||||
✔ GitRepository reconciliation completed
|
||||
✔ fetched revision main/fd2fe8a61d4537bcfa349e4d1dbc480ea699ba8a
|
||||
► annotating Kustomization flux-system in flux-system namespace
|
||||
✔ Kustomization annotated
|
||||
◎ waiting for Kustomization reconciliation
|
||||
✔ Kustomization reconciliation completed
|
||||
✔ reconciled revision main/fd2fe8a61d4537bcfa349e4d1dbc480ea699ba8a
|
||||
$ flux get image repository podinfo-image
|
||||
NAME READY MESSAGE LAST SCAN SUSPENDED
|
||||
podinfo-image True successful scan, found 16 tags 2021-02-08T14:31:38Z False
|
||||
```
|
||||
|
||||
#### Replacing automation annotations
|
||||
|
||||
For each _field_ that's being updated by automation, you'll need an `ImagePolicy` object to describe
|
||||
how to select an image for the field value. In the example, the field `.image` in the container
|
||||
named `"app"` is the field being updated.
|
||||
|
||||
In Flux v1, annotations describe how to select the image to update to, using a prefix. In the
|
||||
example, the prefix is `semver:`:
|
||||
|
||||
```yaml
|
||||
annotations:
|
||||
fluxcd.io/automated: "true"
|
||||
fluxcd.io/tag.app: semver:^5.0
|
||||
```
|
||||
|
||||
These are the prefixes supported in Flux v1, and what to use in Flux v2:
|
||||
|
||||
| Flux v1 prefix | Meaning | Flux v2 equivalent |
|
||||
|----------------|---------|--------------------|
|
||||
| `glob:` | Filter for tags matching the glob pattern, then select the newest by build time | [Use sortable tags](#how-to-use-sortable-image-tags) |
|
||||
| `regex:` | Filter for tags matching the regular expression, then select the newest by build time |[Use sortable tags](#how-to-use-sortable-image-tags) |
|
||||
| `semver:` | Filter for tags that represent versions, and select the highest version in the given range | [Use semver ordering](#how-to-use-semver-image-tags) |
|
||||
|
||||
#### How to use sortable image tags
|
||||
|
||||
To give image tags a useful ordering, you can use a timestamp or serial number as part of each
|
||||
image's tag, then sort either alphabetically or numerically.
|
||||
|
||||
This is a change from Flux v1, in which the build time was fetched from each image's config, and
|
||||
didn't need to be included in the image tag. Therefore, this is likely to require a change to your
|
||||
build process.
|
||||
|
||||
The guide [How to make sortable image tags][image-tags-guide] explains how to change your build
|
||||
process to tag images with a timestamp. This will mean Flux v2 can sort the tags to find the most
|
||||
recently built image.
|
||||
|
||||
##### Filtering the tags in an `ImagePolicy`
|
||||
|
||||
The recommended format for image tags using a timestamp is:
|
||||
|
||||
<branch>-<sha1>-<timestamp>
|
||||
|
||||
The timestamp (or serial number) is the part of the tag that you want to order on. The SHA1 is there
|
||||
so you can trace an image back to the commit from which it was built. You don't need the branch for
|
||||
sorting, but you may want to include only builds from a specific branch.
|
||||
|
||||
Say you want to filter for only images that are from `main` branch, and pick the most recent. Your
|
||||
`ImagePolicy` would look like this:
|
||||
|
||||
```yaml
|
||||
apiVersion: image.toolkit.fluxcd.io/v1alpha1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: my-app-policy
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: podinfo-image
|
||||
filterTags:
|
||||
pattern: '^main-[a-f0-9]+-(?P<ts>[0-9]+)'
|
||||
extract: '$ts'
|
||||
policy:
|
||||
numerical:
|
||||
order: asc
|
||||
```
|
||||
|
||||
The `.spec.pattern` field gives a regular expression that a tag must match to be included. The
|
||||
`.spec.extract` field gives a replacement pattern that can refer back to capture groups in the
|
||||
filter pattern. The extracted values are sorted to find the selected image tag. In this case, the
|
||||
timestamp part of the tag will be extracted and sorted numerically in ascending order. See [the
|
||||
reference docs][imagepolicy-ref] for more examples.
|
||||
|
||||
Once you have made sure you have image tags and an `ImagePolicy` that works, jump ahead to [Checking
|
||||
the ImagePolicy works](#checking-that-the-image-policy-works).
|
||||
|
||||
### How to use SemVer image tags
|
||||
|
||||
The other kind of sorting is by [SemVer][semver], picking the highest version from among those
|
||||
included by the filter. A semver range will also filter for tags that fit in the range. For example,
|
||||
|
||||
```yaml
|
||||
semver:
|
||||
range: ^5.0
|
||||
```
|
||||
|
||||
includes only tags that have a major version of `5`, and selects whichever is the highest.
|
||||
|
||||
This can be combined with a regular expression pattern, to filter on other parts of the tags. For
|
||||
example, you might put a target environment as well as the version in your image tags, like
|
||||
`dev-v1.0.3`.
|
||||
|
||||
Then you would use an `ImagePolicy` similar to this one:
|
||||
|
||||
```yaml
|
||||
apiVersion: image.toolkit.fluxcd.io/v1alpha1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: my-app-policy
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: podinfo-image
|
||||
filterTags:
|
||||
pattern: '^dev-v(?P<version>.*)'
|
||||
extract: '$version'
|
||||
policy:
|
||||
semver:
|
||||
range: '^1.0'
|
||||
```
|
||||
|
||||
Continue on to the next sections to see an example, and how to check that your `ImagePolicy` works.
|
||||
|
||||
#### An `ImagePolicy` for the example
|
||||
|
||||
The example Deployment has annotations using `semver:` as a prefix, so the policy object also uses
|
||||
semver:
|
||||
|
||||
```bash
|
||||
$ # the environment variable $AUTO_PATH was set earlier
|
||||
$ flux create image policy my-app-policy \
|
||||
--image-ref podinfo-image \
|
||||
--semver '^5.0' \
|
||||
--export > ./$AUTO_PATH/my-app-policy.yaml
|
||||
$ cat ./$AUTO_PATH/my-app-policy.yaml
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1alpha1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: my-app-policy
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: podinfo-image
|
||||
policy:
|
||||
semver:
|
||||
range: ^5.0
|
||||
```
|
||||
|
||||
#### Checking that the `ImagePolicy` works
|
||||
|
||||
Commit the manifest file, and push:
|
||||
|
||||
```bash
|
||||
$ git add ./$AUTO_PATH/my-app-policy.yaml
|
||||
$ git commit -s -m "Add image policy for my-app"
|
||||
$ git push
|
||||
# ...
|
||||
```
|
||||
|
||||
Then you can reconcile and check that the image policy works:
|
||||
|
||||
```bash
|
||||
$ flux reconcile kustomization --with-source flux-system
|
||||
► annotating GitRepository flux-system in flux-system namespace
|
||||
✔ GitRepository annotated
|
||||
◎ waiting for GitRepository reconciliation
|
||||
✔ GitRepository reconciliation completed
|
||||
✔ fetched revision main/7dcf50222499be8c97e22cd37e26bbcda8f70b95
|
||||
► annotating Kustomization flux-system in flux-system namespace
|
||||
✔ Kustomization annotated
|
||||
◎ waiting for Kustomization reconciliation
|
||||
✔ Kustomization reconciliation completed
|
||||
✔ reconciled revision main/7dcf50222499be8c97e22cd37e26bbcda8f70b95
|
||||
$ flux get image policy flux-system
|
||||
NAME READY MESSAGE LATEST IMAGE
|
||||
my-app-policy True Latest image tag for 'ghcr.io/stefanprodan/podinfo' resolved to: 5.1.4 ghcr.io/stefanprodan/podinfo:5.1.4
|
||||
```
|
||||
|
||||
### How to mark up files for update
|
||||
|
||||
The last thing to do in each manifest is to mark the fields that you want to be updated.
|
||||
|
||||
In Flux v1, the annotations in a manifest determines the fields to be updated. In the example, the
|
||||
annotations target the image used by the container `app`:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: my-app
|
||||
namespace: default
|
||||
annotations:
|
||||
fluxcd.io/automated: "true"
|
||||
fluxcd.io/tag.app: semver:^5.0 # <-- `.app` here
|
||||
selector:
|
||||
matchLabels:
|
||||
app: podinfo
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: podinfo
|
||||
spec:
|
||||
containers:
|
||||
- name: app # <-- targets `app` here
|
||||
image: ghcr.io/stefanprodan/podinfo:5.0.0
|
||||
```
|
||||
|
||||
This works straight-forwardly for Deployment manifests, but when it comes to `HelmRelease`
|
||||
manifests, it [gets complicated][helm-auto], and it doesn't work at all for many kinds of resources.
|
||||
|
||||
For Flux v2, you mark the field you want to be updated directly, with the namespaced name of the
|
||||
image policy to apply. This is the example Deployment, marked up for Flux v2:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
namespace: default
|
||||
name: my-app
|
||||
selector:
|
||||
matchLabels:
|
||||
app: podinfo
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: podinfo
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: ghcr.io/stefanprodan/podinfo:5.0.0 # {"$imagepolicy": "flux-system:my-app-policy"}
|
||||
```
|
||||
|
||||
The value `flux-system:my-app-policy` names the policy that selects the desired image.
|
||||
|
||||
This works in the same way for `DaemonSet` and `CronJob` manifests. For `HelmRelease` manifests, put
|
||||
the marker alongside the part of the `values` that has the image tag. If the image tag is a separate
|
||||
field, you can put `:tag` on the end of the name, to replace the value with just the selected
|
||||
image's tag. The [image automation guide][image-update-tute-custom] has examples for `HelmRelease`
|
||||
and other custom resources.
|
||||
|
||||
### Committing the marker change and checking that automation works
|
||||
|
||||
Referring to the image policy created earlier, you can see the example Deployment does not use the
|
||||
most recent image. When you commit the manifest file with the update marker added, you would expect
|
||||
automation to update the file.
|
||||
|
||||
Commit the change that adds an update marker:
|
||||
|
||||
```bash
|
||||
$ git add app/my-app.yaml # the filename of the example
|
||||
$ git commit -s -m "Add update marker to my-app manifest"
|
||||
$ git push
|
||||
# ...
|
||||
```
|
||||
|
||||
Now to check that the automation makes a change:
|
||||
|
||||
```bash
|
||||
$ flux reconcile image update my-app-auto
|
||||
► annotating ImageUpdateAutomation my-app-auto in flux-system namespace
|
||||
✔ ImageUpdateAutomation annotated
|
||||
◎ waiting for ImageUpdateAutomation reconciliation
|
||||
✔ ImageUpdateAutomation reconciliation completed
|
||||
✔ committed and pushed a92a4b654f520c00cb6c46b2d5e4fb4861aa58fc
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If a change was not pushed by the image automation, there's several things you can check:
|
||||
|
||||
- it's possible it made a change that is not reported in the latest status -- pull from the origin
|
||||
and check the commit log
|
||||
- check that the name used in the marker corresponds to the namespace and name of an `ImagePolicy`
|
||||
- check that the `ImageUpdateAutomation` is in the same namespace as the `ImagePolicy` objects
|
||||
named in markers
|
||||
- check that the image policy and the image repository are both reported as `Ready`
|
||||
- check that the credentials referenced by the `GitRepository` object have write permission, and
|
||||
create new credentials if necessary.
|
||||
|
||||
As a fallback, you can scan the logs of the automation controller to see if it logged errors:
|
||||
|
||||
```bash
|
||||
$ kubectl logs -n flux-system deploy/image-automation-controller
|
||||
```
|
||||
|
||||
Once you are satisfied that it is working, you can migrate the rest of the manifests using the steps
|
||||
from ["Migrating each manifest to Flux v2"](#migrating-each-manifest-to-flux-v2) above.
|
||||
|
||||
[image-update-tute]: https://toolkit.fluxcd.io/guides/image-update/
|
||||
[imagepolicy-ref]: https://toolkit.fluxcd.io/components/image/imagepolicies/
|
||||
[helm-auto]: https://docs.fluxcd.io/en/1.21.1/references/helm-operator-integration/#automated-image-detection
|
||||
[image-update-tute-custom]: https://toolkit.fluxcd.io/guides/image-update/#configure-image-update-for-custom-resources
|
||||
[flux-v1-migration]: https://toolkit.fluxcd.io/guides/flux-v1-migration/
|
||||
[install-cli]: https://toolkit.fluxcd.io/get-started/#install-the-flux-cli
|
||||
[flux-bootstrap]: https://toolkit.fluxcd.io/guides/installation/#bootstrap
|
||||
[github-pat]: https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token
|
||||
[auto-object-ref]: https://toolkit.fluxcd.io/components/image/imageupdateautomations/
|
||||
[image-update-tute-creds]: https://toolkit.fluxcd.io/guides/image-update/#configure-image-scanning
|
||||
[image-update-tute-clouds]: https://toolkit.fluxcd.io/guides/image-update/#imagerepository-cloud-providers-authentication
|
||||
[image-tags-guide]: https://toolkit.fluxcd.io/guides/sortable-image-tags/
|
||||
[auto-ref]: https://toolkit.fluxcd.io/components/image/imageupdateautomations/
|
||||
[semver]: https://semver.org
|
||||
@@ -1,12 +1,21 @@
|
||||
# Migrate from Flux v1 to v2
|
||||
|
||||
This guide walks you through migrating from Flux v1 to v2.
|
||||
Read the [FAQ](../faq/index.md) to find out what differences are between v1 and v2.
|
||||
Read the [FAQ](faq-migration.md) to find out what differences are between v1 and v2.
|
||||
|
||||
!!! info "Automated image updates"
|
||||
The image automation feature is under development in Flux v2.
|
||||
Please consult the [roadmap](../roadmap/index.md) for more details.
|
||||
|
||||
|
||||
!!! info "Feature parity"
|
||||
"Feature parity" does not mean Flux v2 works exactly the same as v1 (or is
|
||||
backward-compatible); it means you can accomplish the same results, while
|
||||
accounting for the fact that it's a system with a substantially different
|
||||
design.
|
||||
This may at times mean that you have to make adjustments to the way your
|
||||
current cluster configuration is structured. If you are in this situation
|
||||
and need help, please refer to the [support page](https://fluxcd.io/support/).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You will need a Kubernetes cluster version **1.16** or newer
|
||||
@@ -53,6 +62,13 @@ to define the state of your fleet of Kubernetes clusters.
|
||||
|
||||
For a detailed walk-through of the bootstrap procedure please see the [installation guide](installation.md).
|
||||
|
||||
!!! warning "`flux bootstrap` target"
|
||||
`flux bootstrap` should not be run against a Git branch or path
|
||||
that is already being synchronized by Flux v1, as this will make
|
||||
them fight over the resources. Instead, bootstrap to a **new Git
|
||||
repository, branch or path**, and continue with moving the
|
||||
manifests.
|
||||
|
||||
After you've installed Flux v2 on your cluster using bootstrap,
|
||||
you can delete the Flux v1 from your clusters and move the manifests from the
|
||||
Flux v1 repository to the bootstrap one.
|
||||
@@ -109,7 +125,6 @@ Install Flux v2 in the `flux-system` namespace:
|
||||
|
||||
```console
|
||||
$ flux install \
|
||||
--arch=amd64 \
|
||||
--network-policy=true \
|
||||
--watch-all-namespaces=true \
|
||||
--namespace=flux-system
|
||||
@@ -227,7 +242,7 @@ Configure the reconciliation of the `prod` overlay on your cluster:
|
||||
|
||||
```sh
|
||||
flux create kustomization app \
|
||||
--source=app \
|
||||
--source=GitRepository/app \
|
||||
--path="./overlays/prod" \
|
||||
--prune=true \
|
||||
--interval=10m
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user