1
0
mirror of synced 2026-03-01 11:16:56 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Paulo Gomes
35785b8a6f rfc: Add story 2 and alternatives
Signed-off-by: Paulo Gomes <paulo.gomes@weave.works>
2022-10-04 16:06:09 +01:00
Stefan Prodan
650bea497f Add proposal for adding a gating mechanism to Flux
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2022-09-29 17:57:30 +03:00
345 changed files with 5021 additions and 6966 deletions

View File

@@ -10,13 +10,13 @@ pkgbase = flux-bin
license = APACHE license = APACHE
optdepends = bash-completion: auto-completion for flux in Bash optdepends = bash-completion: auto-completion for flux in Bash
optdepends = zsh-completions: auto-completion for flux in ZSH optdepends = zsh-completions: auto-completion for flux in ZSH
source_x86_64 = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_amd64.tar.gz source_x86_64 = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_amd64.tar.gz
sha256sums_x86_64 = ${SHA256SUM_AMD64} sha256sums_x86_64 = ${SHA256SUM_AMD64}
source_armv6h = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm.tar.gz source_armv6h = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_arm.tar.gz
sha256sums_armv6h = ${SHA256SUM_ARM} sha256sums_armv6h = ${SHA256SUM_ARM}
source_armv7h = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm.tar.gz source_armv7h = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_arm.tar.gz
sha256sums_armv7h = ${SHA256SUM_ARM} sha256sums_armv7h = ${SHA256SUM_ARM}
source_aarch64 = flux-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_arm64.tar.gz source_aarch64 = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_arm64.tar.gz
sha256sums_aarch64 = ${SHA256SUM_ARM64} sha256sums_aarch64 = ${SHA256SUM_ARM64}
pkgname = flux-bin pkgname = flux-bin

View File

@@ -4,8 +4,6 @@
pkgname=flux-bin pkgname=flux-bin
pkgver=${PKGVER} pkgver=${PKGVER}
pkgrel=${PKGREL} pkgrel=${PKGREL}
_srcname=flux
_srcver=${VERSION}
pkgdesc="Open and extensible continuous delivery solution for Kubernetes" pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
url="https://fluxcd.io/" url="https://fluxcd.io/"
arch=("x86_64" "armv6h" "armv7h" "aarch64") arch=("x86_64" "armv6h" "armv7h" "aarch64")
@@ -13,16 +11,16 @@ license=("APACHE")
optdepends=('bash-completion: auto-completion for flux in Bash' optdepends=('bash-completion: auto-completion for flux in Bash'
'zsh-completions: auto-completion for flux in ZSH') 'zsh-completions: auto-completion for flux in ZSH')
source_x86_64=( source_x86_64=(
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_linux_amd64.tar.gz" "${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_amd64.tar.gz"
) )
source_armv6h=( source_armv6h=(
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_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=( source_armv7h=(
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_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=( source_aarch64=(
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${_srcver}/flux_${_srcver}_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=( sha256sums_x86_64=(
${SHA256SUM_AMD64} ${SHA256SUM_AMD64}
@@ -36,6 +34,7 @@ sha256sums_armv7h=(
sha256sums_aarch64=( sha256sums_aarch64=(
${SHA256SUM_ARM64} ${SHA256SUM_ARM64}
) )
_srcname=flux
package() { package() {
install -Dm755 ${_srcname} "${pkgdir}/usr/bin/${_srcname}" install -Dm755 ${_srcname} "${pkgdir}/usr/bin/${_srcname}"

View File

@@ -28,7 +28,6 @@ git clone aur@aur.archlinux.org:$PKGNAME $GITDIR 2>&1
CURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }') CURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }')
CURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }') CURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }')
# Transform pre-release to AUR compatible version format
export PKGVER=${VERSION/-/} export PKGVER=${VERSION/-/}
if [[ "${CURRENT_PKGVER}" == "${PKGVER}" ]]; then if [[ "${CURRENT_PKGVER}" == "${PKGVER}" ]]; then
@@ -37,12 +36,12 @@ else
export PKGREL=1 export PKGREL=1
fi fi
export SHA256SUM_ARM=$(sha256sum ${ROOT}/dist/flux_${VERSION}_linux_arm.tar.gz | awk '{ print $1 }') export SHA256SUM_ARM=$(sha256sum ${ROOT}/dist/flux_${PKGVER}_linux_arm.tar.gz | awk '{ print $1 }')
export SHA256SUM_ARM64=$(sha256sum ${ROOT}/dist/flux_${VERSION}_linux_arm64.tar.gz | awk '{ print $1 }') export SHA256SUM_ARM64=$(sha256sum ${ROOT}/dist/flux_${PKGVER}_linux_arm64.tar.gz | awk '{ print $1 }')
export SHA256SUM_AMD64=$(sha256sum ${ROOT}/dist/flux_${VERSION}_linux_amd64.tar.gz | awk '{ print $1 }') export SHA256SUM_AMD64=$(sha256sum ${ROOT}/dist/flux_${PKGVER}_linux_amd64.tar.gz | awk '{ print $1 }')
envsubst '$VERSION $PKGVER $PKGREL $SHA256SUM_AMD64 $SHA256SUM_ARM $SHA256SUM_ARM64' < .SRCINFO.template > $GITDIR/.SRCINFO envsubst '$PKGVER $PKGREL $SHA256SUM_AMD64 $SHA256SUM_ARM $SHA256SUM_ARM64' < .SRCINFO.template > $GITDIR/.SRCINFO
envsubst '$VERSION $PKGVER $PKGREL $SHA256SUM_AMD64 $SHA256SUM_ARM $SHA256SUM_ARM64' < PKGBUILD.template > $GITDIR/PKGBUILD envsubst '$PKGVER $PKGREL $SHA256SUM_AMD64 $SHA256SUM_ARM $SHA256SUM_ARM64' < PKGBUILD.template > $GITDIR/PKGBUILD
cd $GITDIR cd $GITDIR
git config user.name "fluxcdbot" git config user.name "fluxcdbot"

View File

@@ -13,6 +13,6 @@ pkgbase = flux-go
provides = flux-bin provides = flux-bin
conflicts = flux-bin conflicts = flux-bin
replaces = flux-cli replaces = flux-cli
source = flux-go-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/archive/v${VERSION}.tar.gz source = flux-go-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/archive/v${PKGVER}.tar.gz
pkgname = flux-go pkgname = flux-go

View File

@@ -4,8 +4,6 @@
pkgname=flux-go pkgname=flux-go
pkgver=${PKGVER} pkgver=${PKGVER}
pkgrel=${PKGREL} pkgrel=${PKGREL}
_srcname=flux
_srcver=${VERSION}
pkgdesc="Open and extensible continuous delivery solution for Kubernetes" pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
url="https://fluxcd.io/" url="https://fluxcd.io/"
arch=("x86_64" "armv6h" "armv7h" "aarch64") arch=("x86_64" "armv6h" "armv7h" "aarch64")
@@ -18,25 +16,26 @@ makedepends=('go>=1.17', 'kustomize>=3.0')
optdepends=('bash-completion: auto-completion for flux in Bash', optdepends=('bash-completion: auto-completion for flux in Bash',
'zsh-completions: auto-completion for flux in ZSH') 'zsh-completions: auto-completion for flux in ZSH')
source=( source=(
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/archive/v${_srcver}.tar.gz" "${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/archive/v${pkgver}.tar.gz"
) )
sha256sums=( sha256sums=(
${SHA256SUM} ${SHA256SUM}
) )
_srcname=flux
build() { build() {
cd "flux2-${_srcver}" cd "flux2-${pkgver}"
export CGO_LDFLAGS="$LDFLAGS" export CGO_LDFLAGS="$LDFLAGS"
export CGO_CFLAGS="$CFLAGS" export CGO_CFLAGS="$CFLAGS"
export CGO_CXXFLAGS="$CXXFLAGS" export CGO_CXXFLAGS="$CXXFLAGS"
export CGO_CPPFLAGS="$CPPFLAGS" export CGO_CPPFLAGS="$CPPFLAGS"
export GOFLAGS="-buildmode=pie -trimpath -mod=readonly -modcacherw" export GOFLAGS="-buildmode=pie -trimpath -mod=readonly -modcacherw"
make cmd/flux/.manifests.done make cmd/flux/.manifests.done
go build -ldflags "-linkmode=external -X main.VERSION=${_srcver}" -o ${_srcname} ./cmd/flux go build -ldflags "-linkmode=external -X main.VERSION=${pkgver}" -o ${_srcname} ./cmd/flux
} }
check() { check() {
cd "flux2-${_srcver}" cd "flux2-${pkgver}"
case $CARCH in case $CARCH in
aarch64) aarch64)
export ENVTEST_ARCH=arm64 export ENVTEST_ARCH=arm64
@@ -49,7 +48,7 @@ check() {
} }
package() { package() {
cd "flux2-${_srcver}" cd "flux2-${pkgver}"
install -Dm755 ${_srcname} "${pkgdir}/usr/bin/${_srcname}" install -Dm755 ${_srcname} "${pkgdir}/usr/bin/${_srcname}"
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"

View File

@@ -28,7 +28,6 @@ git clone aur@aur.archlinux.org:$PKGNAME $GITDIR 2>&1
CURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }') CURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }')
CURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }') CURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }')
# Transform pre-release to AUR compatible version format
export PKGVER=${VERSION/-/} export PKGVER=${VERSION/-/}
if [[ "${CURRENT_PKGVER}" == "${PKGVER}" ]]; then if [[ "${CURRENT_PKGVER}" == "${PKGVER}" ]]; then
@@ -37,10 +36,10 @@ else
export PKGREL=1 export PKGREL=1
fi fi
export SHA256SUM=$(curl -sL https://github.com/fluxcd/flux2/archive/v${VERSION}.tar.gz | sha256sum | awk '{ print $1 }') export SHA256SUM=$(curl -sL https://github.com/fluxcd/flux2/archive/v$PKGVER.tar.gz | sha256sum | awk '{ print $1 }')
envsubst '$VERSION $PKGVER $PKGREL $SHA256SUM' < .SRCINFO.template > $GITDIR/.SRCINFO envsubst '$PKGVER $PKGREL $SHA256SUM' < .SRCINFO.template > $GITDIR/.SRCINFO
envsubst '$VERSION $PKGVER $PKGREL $SHA256SUM' < PKGBUILD.template > $GITDIR/PKGBUILD envsubst '$PKGVER $PKGREL $SHA256SUM' < PKGBUILD.template > $GITDIR/PKGBUILD
cd $GITDIR cd $GITDIR
git config user.name "fluxcdbot" git config user.name "fluxcdbot"

View File

@@ -4,7 +4,6 @@
pkgname=flux-scm pkgname=flux-scm
pkgver=${PKGVER} pkgver=${PKGVER}
pkgrel=${PKGREL} pkgrel=${PKGREL}
_srcname=flux
pkgdesc="Open and extensible continuous delivery solution for Kubernetes" pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
url="https://fluxcd.io/" url="https://fluxcd.io/"
arch=("x86_64" "armv6h" "armv7h" "aarch64") arch=("x86_64" "armv6h" "armv7h" "aarch64")
@@ -19,6 +18,7 @@ source=(
"git+https://github.com/fluxcd/flux2.git" "git+https://github.com/fluxcd/flux2.git"
) )
md5sums=('SKIP') md5sums=('SKIP')
_srcname=flux
pkgver() { pkgver() {
cd "flux2" cd "flux2"

View File

@@ -28,7 +28,6 @@ git clone aur@aur.archlinux.org:$PKGNAME $GITDIR 2>&1
CURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }') CURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }')
CURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }') CURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }')
# Transform pre-release to AUR compatible version format
export PKGVER=${VERSION/-/} export PKGVER=${VERSION/-/}
if [[ "${CURRENT_PKGVER}" == "${PKGVER}" ]]; then if [[ "${CURRENT_PKGVER}" == "${PKGVER}" ]]; then

View File

@@ -1,9 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
labels: ["area/build"]
schedule:
# by default this will be on a monday.
interval: "weekly"

View File

@@ -1,32 +1,24 @@
# Flux ARM64 GitHub runners # Flux ARM64 GitHub runners
The Flux ARM64 end-to-end tests run on Equinix Metal instances provisioned with Docker and GitHub self-hosted runners. The Flux ARM64 end-to-end tests run on Equinix instances provisioned with Docker and GitHub self-hosted runners.
## Current instances ## Current instances
| Repository | Runner | Instance | Location | | Runner | Instance | Region |
|-----------------------------|------------------|------------------------|---------------| |---------------|---------------------|--------|
| flux2 | equinix-arm-dc-1 | flux-equinix-arm-dc-01 | Washington DC | | equinix-arm-1 | flux-equinix-arm-01 | AMS1 |
| flux2 | equinix-arm-dc-2 | flux-equinix-arm-dc-01 | Washington DC | | equinix-arm-2 | flux-equinix-arm-01 | AMS1 |
| flux2 | equinix-arm-da-1 | flux-equinix-arm-da-01 | Dallas | | equinix-arm-3 | flux-equinix-arm-01 | AMS1 |
| flux2 | equinix-arm-da-2 | flux-equinix-arm-da-01 | Dallas | | equinix-arm-4 | flux-equinix-arm-02 | DFW2 |
| source-controller | equinix-arm-dc-1 | flux-equinix-arm-dc-01 | Washington DC | | equinix-arm-5 | flux-equinix-arm-02 | DFW2 |
| source-controller | equinix-arm-da-1 | flux-equinix-arm-da-01 | Dallas | | equinix-arm-6 | flux-equinix-arm-02 | DFW2 |
| image-automation-controller | equinix-arm-dc-1 | flux-equinix-arm-dc-01 | Washington DC |
| image-automation-controller | equinix-arm-da-1 | flux-equinix-arm-da-01 | Dallas |
Instance spec:
- Ampere Altra Q80-30 80-core processor @ 2.8GHz
- 2 x 960GB NVME
- 256GB RAM
- 2 x 25Gbps
## Instance setup ## Instance setup
In order to add a new runner to the GitHub Actions pool, In order to add a new runner to the GitHub Actions pool,
first create a server on Equinix with the following configuration: first create a server on Equinix with the following configuration:
- Type: `c3.large.arm64` - Type: c2.large.arm
- OS: `Ubuntu 22.04 LTS` - OS: Ubuntu 20.04
### Install prerequisites ### Install prerequisites
@@ -62,14 +54,14 @@ sudo ./prereq.sh
- Retrieve the GitHub runner token from the repository [settings page](https://github.com/fluxcd/flux2/settings/actions/runners/new?arch=arm64&os=linux) - Retrieve the GitHub runner token from the repository [settings page](https://github.com/fluxcd/flux2/settings/actions/runners/new?arch=arm64&os=linux)
- Create two directories `flux2-01`, `flux2-02` - Create 3 directories `runner1`, `runner2`, `runner3`
- In each dir run: - In each dir run:
```shell ```shell
curl -sL https://raw.githubusercontent.com/fluxcd/flux2/main/.github/runners/runner-setup.sh > runner-setup.sh \ curl -sL https://raw.githubusercontent.com/fluxcd/flux2/main/.github/runners/runner-setup.sh > runner-setup.sh \
&& chmod +x ./runner-setup.sh && chmod +x ./runner-setup.sh
./runner-setup.sh equinix-arm-<NUMBER> <TOKEN> <REPO> ./runner-setup.sh equinix-arm-<NUMBER> <TOKEN>
``` ```
- Reboot the instance - Reboot the instance

View File

@@ -18,11 +18,11 @@
set -eu set -eu
KIND_VERSION=0.17.0 KIND_VERSION=0.14.0
KUBECTL_VERSION=1.24.0 KUBECTL_VERSION=1.24.0
KUSTOMIZE_VERSION=4.5.7 KUSTOMIZE_VERSION=4.5.4
HELM_VERSION=3.10.1 HELM_VERSION=3.8.2
GITHUB_RUNNER_VERSION=2.298.2 GITHUB_RUNNER_VERSION=2.291.1
PACKAGES="apt-transport-https ca-certificates software-properties-common build-essential libssl-dev gnupg lsb-release jq pkg-config" PACKAGES="apt-transport-https ca-certificates software-properties-common build-essential libssl-dev gnupg lsb-release jq pkg-config"
# install prerequisites # install prerequisites
@@ -31,10 +31,6 @@ apt-get update \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# fix Kubernetes DNS resolution
rm /etc/resolv.conf
cat "/run/systemd/resolve/stub-resolv.conf" | sed '/search/d' > /etc/resolv.conf
# install docker # install docker
curl -fsSL https://get.docker.com -o get-docker.sh \ curl -fsSL https://get.docker.com -o get-docker.sh \
&& chmod +x get-docker.sh && chmod +x get-docker.sh

View File

@@ -22,7 +22,7 @@ RUNNER_NAME=$1
REPOSITORY_TOKEN=$2 REPOSITORY_TOKEN=$2
REPOSITORY_URL=${3:-https://github.com/fluxcd/flux2} REPOSITORY_URL=${3:-https://github.com/fluxcd/flux2}
GITHUB_RUNNER_VERSION=2.298.2 GITHUB_RUNNER_VERSION=2.285.1
# download runner # download runner
curl -o actions-runner-linux-arm64.tar.gz -L https://github.com/actions/runner/releases/download/v${GITHUB_RUNNER_VERSION}/actions-runner-linux-arm64-${GITHUB_RUNNER_VERSION}.tar.gz \ curl -o actions-runner-linux-arm64.tar.gz -L https://github.com/actions/runner/releases/download/v${GITHUB_RUNNER_VERSION}/actions-runner-linux-arm64-${GITHUB_RUNNER_VERSION}.tar.gz \

View File

@@ -1,50 +0,0 @@
# Flux GitHub Workflows
## End-to-end Testing
The e2e workflows run a series of tests to ensure that the Flux CLI and
the GitOps Toolkit controllers work well all together.
The tests are written in Go, Bash, Make and Terraform.
| Workflow | Jobs | Runner | Role |
|--------------------|----------------------|----------------|-----------------------------------------------|
| e2e.yaml | e2e-amd64-kubernetes | GitHub Ubuntu | integration testing with Kubernetes Kind<br/> |
| e2e-arm64.yaml | e2e-arm64-kubernetes | Equinix Ubuntu | integration testing with Kubernetes Kind<br/> |
| e2e-bootstrap.yaml | e2e-boostrap-github | GitHub Ubuntu | integration testing with GitHub API<br/> |
| e2e-azure.yaml | e2e-amd64-aks | GitHub Ubuntu | integration testing with Azure API<br/> |
| scan.yaml | scan-fossa | GitHub Ubuntu | license scanning<br/> |
| scan.yaml | scan-snyk | GitHub Ubuntu | vulnerability scanning<br/> |
| scan.yaml | scan-codeql | GitHub Ubuntu | vulnerability scanning<br/> |
## Components Update
The components update workflow scans the GitOps Toolkit controller repositories for new releases,
amd when it finds a new controller version, the workflow performs the following steps:
- Updates the controller API package version in `go.mod`.
- Patches the controller CRDs version in the `manifests/crds` overlay.
- Patches the controller Deployment version in `manifests/bases` overlay.
- Opens a Pull Request against the `main` branch.
- Triggers the e2e test suite to run for the opened PR.
| Workflow | Jobs | Runner | Role |
|-------------|-------------------|---------------|-----------------------------------------------------|
| update.yaml | update-components | GitHub Ubuntu | update the GitOps Toolkit APIs and controllers<br/> |
## Release
The release workflow is triggered by a semver Git tag and performs the following steps:
- Generates the Flux install manifests (YAML).
- Generates the OpenAPI validation schemas for the GitOps Toolkit CRDs (JSON).
- Generates a Software Bill of Materials (SPDX JSON).
- Builds the Flux CLI binaries and the multi-arch container images.
- Pushes the container images to GitHub Container Registry and DockerHub.
- Signs the sbom, the binaries checksum and the container images with Cosign and GitHub OIDC.
- Uploads the sbom, binaries, checksums and install manifests to GitHub Releases.
- Pushes the install manifests as OCI artifacts to GitHub Container Registry and DockerHub.
- Signs the OCI artifacts with Cosign and GitHub OIDC.
| Workflow | Jobs | Runner | Role |
|--------------|------------------------|---------------|------------------------------------------------------|
| release.yaml | release-flux-cli | GitHub Ubuntu | build, push and sign the CLI release artifacts<br/> |
| release.yaml | release-flux-manifests | GitHub Ubuntu | build, push and sign the Flux install manifests<br/> |

View File

@@ -1,40 +1,36 @@
name: e2e-bootstrap name: bootstrap
on: on:
workflow_dispatch:
push: push:
branches: [ main, release-* ] branches: [ main ]
pull_request: pull_request:
branches: [ main, release-* ] branches: [ main ]
permissions:
contents: read
jobs: jobs:
e2e-boostrap-github: github:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 uses: actions/checkout@v3
- name: Restore Go cache
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go1.18-
- name: Setup Go - name: Setup Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 uses: actions/setup-go@v3
with: with:
go-version: 1.20.x go-version: 1.19.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@fa81e57adff234b2908110485695db0f181f3c67 # v1.7.0 uses: engineerd/setup-kind@v0.5.0
with: with:
version: v0.17.0 version: v0.16.0
cluster_name: kind image: kindest/node:v1.25.2@sha256:9be91e9e9cdf116809841fc77ebdb8845443c4c72fe5218f3ae9eb57fdb4bace
# The versions below should target the newest Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes
node_image: kindest/node:v1.26.0
kubectl_version: v1.26.2
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main uses: fluxcd/pkg//actions/kustomize@main
- name: Build - name: Build
run: | run: |
make cmd/flux/.manifests.done make cmd/flux/.manifests.done
@@ -47,7 +43,7 @@ jobs:
COMMIT_SHA=$(git rev-parse HEAD) COMMIT_SHA=$(git rev-parse HEAD)
PSEUDO_RAND_SUFFIX=$(echo "${BRANCH_NAME}-${COMMIT_SHA}" | shasum | awk '{print $1}') PSEUDO_RAND_SUFFIX=$(echo "${BRANCH_NAME}-${COMMIT_SHA}" | shasum | awk '{print $1}')
TEST_REPO_NAME="${REPOSITORY_NAME}-${PSEUDO_RAND_SUFFIX}" TEST_REPO_NAME="${REPOSITORY_NAME}-${PSEUDO_RAND_SUFFIX}"
echo "test_repo_name=$TEST_REPO_NAME" >> $GITHUB_OUTPUT echo "::set-output name=test_repo_name::$TEST_REPO_NAME"
- name: bootstrap init - name: bootstrap init
run: | run: |
/tmp/flux bootstrap github --manifests ./manifests/install/ \ /tmp/flux bootstrap github --manifests ./manifests/install/ \
@@ -84,6 +80,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
GITHUB_REPO_NAME: ${{ steps.vars.outputs.test_repo_name }} GITHUB_REPO_NAME: ${{ steps.vars.outputs.test_repo_name }}
GITHUB_ORG_NAME: fluxcd-testing GITHUB_ORG_NAME: fluxcd-testing
- name: libgit2
run: |
/tmp/flux create source git test-libgit2 \
--url=ssh://git@github.com/fluxcd-testing/${{ steps.vars.outputs.test_repo_name }} \
--git-implementation=libgit2 \
--secret-ref=flux-system \
--branch=main
- name: uninstall - name: uninstall
run: | run: |
/tmp/flux uninstall -s --keep-namespace /tmp/flux uninstall -s --keep-namespace

View File

@@ -3,101 +3,33 @@ name: e2e-arm64
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: [ main, update-components, e2e-*, release-* ] branches: [ main, update-components ]
permissions:
contents: read
jobs: jobs:
e2e-arm64-kubernetes: test:
# Hosted on Equinix # Hosted on Equinix
# Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners # Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners
runs-on: [self-hosted, Linux, ARM64, equinix] runs-on: [self-hosted, Linux, ARM64, equinix]
strategy:
matrix:
# Keep this list up-to-date with https://endoflife.date/kubernetes
# Check which versions are available on DockerHub with 'crane ls kindest/node'
KUBERNETES_VERSION: [ 1.25.8, 1.26.3, 1.27.1 ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 uses: actions/checkout@v3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 uses: actions/setup-go@v3
with: with:
go-version: 1.20.x go-version: 1.19.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Prepare - name: Prepare
id: prep id: prep
run: | run: |
ID=${GITHUB_SHA:0:7}-${{ matrix.KUBERNETES_VERSION }}-$(date +%s) echo ::set-output name=CLUSTER::arm64-${GITHUB_SHA:0:7}-$(date +%s)
echo "CLUSTER=arm64-${ID}" >> $GITHUB_OUTPUT echo ::set-output name=CONTEXT::kind-arm64-${GITHUB_SHA:0:7}-$(date +%s)
- name: Build - name: Build
run: | run: |
make build make build
- name: Setup Kubernetes Kind - name: Setup Kubernetes Kind
run: | run: |
kind create cluster \ kind create cluster --name ${{ steps.prep.outputs.CLUSTER }} --kubeconfig=/tmp/${{ steps.prep.outputs.CLUSTER }}
--wait 5m \
--name ${{ steps.prep.outputs.CLUSTER }} \
--kubeconfig=/tmp/${{ steps.prep.outputs.CLUSTER }} \
--image=kindest/node:v${{ matrix.KUBERNETES_VERSION }}
- name: Run e2e tests - name: Run e2e tests
run: TEST_KUBECONFIG=/tmp/${{ steps.prep.outputs.CLUSTER }} make e2e run: TEST_KUBECONFIG=/tmp/${{ steps.prep.outputs.CLUSTER }} make e2e
- name: Run multi-tenancy tests
env:
KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }}
run: |
./bin/flux install
./bin/flux create source git flux-system \
--interval=15m \
--url=https://github.com/fluxcd/flux2-multi-tenancy \
--branch=main \
--ignore-paths="./clusters/**/flux-system/"
./bin/flux create kustomization flux-system \
--interval=15m \
--source=flux-system \
--path=./clusters/staging
kubectl -n flux-system wait kustomization/tenants --for=condition=ready --timeout=5m
kubectl -n apps wait kustomization/dev-team --for=condition=ready --timeout=1m
kubectl -n apps wait helmrelease/podinfo --for=condition=ready --timeout=1m
- name: Run monitoring tests
# Keep this test in sync with https://fluxcd.io/flux/guides/monitoring/
env:
KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }}
run: |
./bin/flux create source git flux-monitoring \
--interval=30m \
--url=https://github.com/fluxcd/flux2 \
--branch=${GITHUB_REF#refs/heads/}
./bin/flux create kustomization kube-prometheus-stack \
--interval=1h \
--prune \
--source=flux-monitoring \
--path="./manifests/monitoring/kube-prometheus-stack" \
--health-check-timeout=5m \
--wait
./bin/flux create kustomization monitoring-config \
--depends-on=kube-prometheus-stack \
--interval=1h \
--prune=true \
--source=flux-monitoring \
--path="./manifests/monitoring/monitoring-config" \
--health-check-timeout=1m \
--wait
kubectl -n flux-system wait kustomization/kube-prometheus-stack --for=condition=ready --timeout=5m
kubectl -n flux-system wait kustomization/monitoring-config --for=condition=ready --timeout=5m
kubectl -n monitoring wait helmrelease/kube-prometheus-stack --for=condition=ready --timeout=1m
- name: Debug failure
if: failure()
env:
KUBECONFIG: /tmp/${{ steps.prep.outputs.CLUSTER }}
run: |
kubectl -n flux-system get all
kubectl -n flux-system describe po
kubectl -n flux-system logs deploy/source-controller
kubectl -n flux-system logs deploy/kustomize-controller
- name: Cleanup - name: Cleanup
if: always() if: always()
run: | run: |

View File

@@ -5,35 +5,33 @@ on:
schedule: schedule:
- cron: '0 6 * * *' - cron: '0 6 * * *'
push: push:
branches: branches: [ azure* ]
- main
paths:
- 'tests/**'
- '.github/workflows/e2e-azure.yaml'
pull_request:
branches:
- main
paths:
- 'tests/**'
- '.github/workflows/e2e-azure.yaml'
permissions:
contents: read
jobs: jobs:
e2e-amd64-aks: e2e:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 uses: actions/checkout@v3
- name: Setup Go - name: Restore Go cache
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 uses: actions/cache@v3
with: with:
go-version: 1.20.x path: ~/go/pkg/mod
cache-dependency-path: | key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
**/go.sum restore-keys: |
**/go.mod ${{ runner.os }}-go1.18-
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.19.x
- name: Install libgit2
run: |
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 648ACFD622F3D138
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 0E98404D386FA1D9
echo "deb http://deb.debian.org/debian unstable main" | sudo tee -a /etc/apt/sources.list
echo "deb-src http://deb.debian.org/debian unstable main" | sudo tee -a /etc/apt/sources.list
sudo apt-get update
sudo apt-get install -y --allow-downgrades libgit2-dev/unstable zlib1g-dev/unstable libssh2-1-dev/unstable libpcre3-dev/unstable
- name: Setup Flux CLI - name: Setup Flux CLI
run: | run: |
make build make build
@@ -46,7 +44,7 @@ jobs:
mkdir -p $HOME/.local/bin mkdir -p $HOME/.local/bin
mv sops-v3.7.1.linux $HOME/.local/bin/sops mv sops-v3.7.1.linux $HOME/.local/bin/sops
- name: Setup Terraform - name: Setup Terraform
uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2 uses: hashicorp/setup-terraform@v1
with: with:
terraform_version: 1.2.8 terraform_version: 1.2.8
terraform_wrapper: false terraform_wrapper: false

View File

@@ -1,17 +1,13 @@
name: e2e name: e2e
on: on:
workflow_dispatch:
push: push:
branches: [ main, release-* ] branches: [ main ]
pull_request: pull_request:
branches: [ main, release-* ] branches: [ main, oci ]
permissions:
contents: read
jobs: jobs:
e2e-amd64-kubernetes: kind:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
registry: registry:
@@ -20,30 +16,30 @@ jobs:
- 5000:5000 - 5000:5000
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 uses: actions/checkout@v3
- name: Restore Go cache
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go1.18-
- name: Setup Go - name: Setup Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 uses: actions/setup-go@v3
with: with:
go-version: 1.20.x go-version: 1.19.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@fa81e57adff234b2908110485695db0f181f3c67 # v1.7.0 uses: engineerd/setup-kind@v0.5.0
with: with:
version: v0.17.0 version: v0.11.1
cluster_name: kind image: kindest/node:v1.20.7
config: .github/kind/config.yaml # disable KIND-net config: .github/kind/config.yaml # disable KIND-net
# The versions below should target the newest Kubernetes version
# Keep this up-to-date with https://endoflife.date/kubernetes
node_image: kindest/node:v1.26.0
kubectl_version: v1.26.2
- name: Setup Calico for network policy - name: Setup Calico for network policy
run: | run: |
kubectl apply -f https://docs.projectcalico.org/v3.25/manifests/calico.yaml kubectl apply -f https://docs.projectcalico.org/v3.20/manifests/calico.yaml
kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main uses: fluxcd/pkg//actions/kustomize@main
- name: Run tests - name: Run tests
run: make test run: make test
- name: Run e2e tests - name: Run e2e tests
@@ -77,14 +73,21 @@ jobs:
run: | run: |
/tmp/flux create source git podinfo \ /tmp/flux create source git podinfo \
--url https://github.com/stefanprodan/podinfo \ --url https://github.com/stefanprodan/podinfo \
--tag-semver=">=6.3.5" --tag-semver=">=3.2.3"
- name: flux create source git export apply - name: flux create source git export apply
run: | run: |
/tmp/flux create source git podinfo-export \ /tmp/flux create source git podinfo-export \
--url https://github.com/stefanprodan/podinfo \ --url https://github.com/stefanprodan/podinfo \
--tag-semver=">=6.3.5" \ --tag-semver=">=3.2.3" \
--export | kubectl apply -f - --export | kubectl apply -f -
/tmp/flux delete source git podinfo-export --silent /tmp/flux delete source git podinfo-export --silent
- name: flux create source git libgit2 semver
run: |
/tmp/flux create source git podinfo-libgit2 \
--url https://github.com/stefanprodan/podinfo \
--tag-semver=">=3.2.3" \
--git-implementation=libgit2
/tmp/flux delete source git podinfo-libgit2 --silent
- name: flux get sources git - name: flux get sources git
run: | run: |
/tmp/flux get sources git /tmp/flux get sources git
@@ -139,7 +142,7 @@ jobs:
--target-namespace=default \ --target-namespace=default \
--source=HelmRepository/podinfo.flux-system \ --source=HelmRepository/podinfo.flux-system \
--chart=podinfo \ --chart=podinfo \
--chart-version=">6.0.0 <7.0.0" --chart-version=">4.0.0 <5.0.0"
- name: flux create helmrelease --source=GitRepository/podinfo - name: flux create helmrelease --source=GitRepository/podinfo
run: | run: |
/tmp/flux create hr podinfo-git \ /tmp/flux create hr podinfo-git \
@@ -175,7 +178,7 @@ jobs:
/tmp/flux push artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \ /tmp/flux push artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
--path="./manifests" \ --path="./manifests" \
--source="${{ github.repositoryUrl }}" \ --source="${{ github.repositoryUrl }}" \
--revision="${{ github.ref }}@sha1:${{ github.sha }}" --revision="${{ github.ref }}/${{ github.sha }}"
/tmp/flux tag artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \ /tmp/flux tag artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
--tag latest --tag latest
/tmp/flux list artifacts oci://localhost:5000/fluxcd/flux /tmp/flux list artifacts oci://localhost:5000/fluxcd/flux
@@ -183,11 +186,11 @@ jobs:
run: | run: |
/tmp/flux create source oci podinfo-oci \ /tmp/flux create source oci podinfo-oci \
--url oci://ghcr.io/stefanprodan/manifests/podinfo \ --url oci://ghcr.io/stefanprodan/manifests/podinfo \
--tag-semver 6.3.x \ --tag-semver 6.1.x \
--interval 10m --interval 10m
/tmp/flux create kustomization podinfo-oci \ /tmp/flux create kustomization podinfo-oci \
--source=OCIRepository/podinfo-oci \ --source=OCIRepository/podinfo-oci \
--path="./" \ --path="./kustomize" \
--prune=true \ --prune=true \
--interval=5m \ --interval=5m \
--target-namespace=default \ --target-namespace=default \
@@ -208,20 +211,21 @@ jobs:
/tmp/flux -n apps create hr podinfo-helm \ /tmp/flux -n apps create hr podinfo-helm \
--source=HelmRepository/podinfo \ --source=HelmRepository/podinfo \
--chart=podinfo \ --chart=podinfo \
--chart-version="6.3.x" \ --chart-version="5.0.x" \
--service-account=dev-team --service-account=dev-team
- name: flux2-kustomize-helm-example - name: flux2-kustomize-helm-example
run: | run: |
/tmp/flux create source git flux-system \ /tmp/flux create source git flux-system \
--url=https://github.com/fluxcd/flux2-kustomize-helm-example \ --url=https://github.com/fluxcd/flux2-kustomize-helm-example \
--branch=main \ --branch=main \
--ignore-paths="./clusters/**/flux-system/" \
--recurse-submodules --recurse-submodules
/tmp/flux create kustomization flux-system \ /tmp/flux create kustomization flux-system \
--source=flux-system \ --source=flux-system \
--path=./clusters/staging --path=./clusters/staging
kubectl -n flux-system wait kustomization/infra-controllers --for=condition=ready --timeout=5m kubectl -n flux-system wait kustomization/infrastructure --for=condition=ready --timeout=5m
kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=5m kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=5m
kubectl -n nginx wait helmrelease/nginx --for=condition=ready --timeout=5m
kubectl -n redis wait helmrelease/redis --for=condition=ready --timeout=5m
kubectl -n podinfo wait helmrelease/podinfo --for=condition=ready --timeout=5m kubectl -n podinfo wait helmrelease/podinfo --for=condition=ready --timeout=5m
- name: flux tree - name: flux tree
run: | run: |

View File

@@ -1,39 +0,0 @@
name: ossf
on:
workflow_dispatch:
push:
branches:
- main
schedule:
# Weekly on Saturdays.
- cron: '30 1 * * 6'
permissions: read-all
jobs:
scorecard:
runs-on: ubuntu-latest
permissions:
security-events: write
id-token: write
actions: read
contents: read
steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Run analysis
uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # v2.1.3
with:
results_file: results.sarif
results_format: sarif
repo_token: ${{ secrets.GITHUB_TOKEN }}
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@0225834cc549ee0ca93cb085b92954821a145866 # v2.3.5
with:
sarif_file: results.sarif

73
.github/workflows/release-manifests.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
name: release-manifests
on:
release:
types: [published]
workflow_dispatch:
permissions:
id-token: write # needed for keyless signing
packages: write # needed for ghcr access
jobs:
build-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Setup Flux CLI
uses: ./action/
- name: Prepare
id: prep
run: |
VERSION=$(flux version --client | awk '{ print $NF }')
echo ::set-output name=VERSION::${VERSION}
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
- name: Push manifests to GHCR
run: |
mkdir -p ./ghcr.io/flux-system
flux install --registry=ghcr.io/fluxcd \
--components-extra=image-reflector-controller,image-automation-controller \
--export > ./ghcr.io/flux-system/gotk-components.yaml
cd ./ghcr.io && flux push artifact \
oci://ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }} \
--path="./flux-system" \
--source=${{ github.repositoryUrl }} \
--revision="${{ github.ref_name }}/${{ github.sha }}"
- name: Push manifests to DockerHub
run: |
mkdir -p ./docker.io/flux-system
flux install --registry=docker.io/fluxcd \
--components-extra=image-reflector-controller,image-automation-controller \
--export > ./docker.io/flux-system/gotk-components.yaml
cd ./docker.io && flux push artifact \
oci://docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }} \
--path="./flux-system" \
--source=${{ github.repositoryUrl }} \
--revision="${{ github.ref_name }}/${{ github.sha }}"
- uses: sigstore/cosign-installer@main
- name: Sign manifests
env:
COSIGN_EXPERIMENTAL: 1
run: |
cosign sign ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }}
cosign sign docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }}
- name: Tag manifests
run: |
flux tag artifact oci://ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }} \
--tag latest
flux tag artifact oci://docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.VERSION }} \
--tag latest

View File

@@ -5,44 +5,41 @@ on:
tags: [ 'v*' ] tags: [ 'v*' ]
permissions: permissions:
contents: read contents: write # needed to write releases
id-token: write # needed for keyless signing
packages: write # needed for ghcr access
jobs: jobs:
release-flux-cli: goreleaser:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write # needed to write releases
id-token: write # needed for keyless signing
packages: write # needed for ghcr access
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 uses: actions/checkout@v3
- name: Unshallow - name: Unshallow
run: git fetch --prune --unshallow run: git fetch --prune --unshallow
- name: Setup Go - name: Setup Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 uses: actions/setup-go@v3
with: with:
go-version: 1.20.x go-version: 1.19.x
cache: false
- name: Setup QEMU - name: Setup QEMU
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0 uses: docker/setup-qemu-action@v2
- name: Setup Docker Buildx - name: Setup Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2.5.0 uses: docker/setup-buildx-action@v2
- name: Setup Syft - name: Setup Syft
uses: anchore/sbom-action/download-syft@4d571ad1038a9cc29d676154ef265ab8f9027042 # v0.14.2 uses: anchore/sbom-action/download-syft@v0
- name: Setup Cosign - name: Setup Cosign
uses: sigstore/cosign-installer@dd6b2e2b610a11fd73dd187a43d57cc1394e35f9 # v3.0.5 uses: sigstore/cosign-installer@main
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main uses: fluxcd/pkg//actions/kustomize@main
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: fluxcdbot username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }} password: ${{ secrets.GHCR_TOKEN }}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0 uses: docker/login-action@v2
with: with:
username: fluxcdbot username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }} password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@@ -54,8 +51,10 @@ jobs:
- name: Build CRDs - name: Build CRDs
run: | run: |
kustomize build manifests/crds > all-crds.yaml kustomize build manifests/crds > all-crds.yaml
# Pinned to commit before https://github.com/fluxcd/pkg/pull/189 due to
# introduction faulty behavior.
- name: Generate OpenAPI JSON schemas from CRDs - name: Generate OpenAPI JSON schemas from CRDs
uses: fluxcd/pkg/actions/crdjsonschema@main uses: fluxcd/pkg//actions/crdjsonschema@49e26aa2ee9e734c3233c560253fd9542afe18ae
with: with:
crd: all-crds.yaml crd: all-crds.yaml
output: schemas output: schemas
@@ -74,7 +73,7 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4.2.0 uses: goreleaser/goreleaser-action@v3
with: with:
version: latest version: latest
args: release --release-notes=output/notes.md --skip-validate args: release --release-notes=output/notes.md --skip-validate
@@ -82,69 +81,3 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
AUR_BOT_SSH_PRIVATE_KEY: ${{ secrets.AUR_BOT_SSH_PRIVATE_KEY }} AUR_BOT_SSH_PRIVATE_KEY: ${{ secrets.AUR_BOT_SSH_PRIVATE_KEY }}
release-flux-manifests:
runs-on: ubuntu-latest
needs: release-flux-cli
permissions:
id-token: write
packages: write
steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Setup Flux CLI
uses: ./action/
- name: Prepare
id: prep
run: |
VERSION=$(flux version --client | awk '{ print $NF }')
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Login to GHCR
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
- name: Push manifests to GHCR
run: |
mkdir -p ./ghcr.io/flux-system
flux install --registry=ghcr.io/fluxcd \
--components-extra=image-reflector-controller,image-automation-controller \
--export > ./ghcr.io/flux-system/gotk-components.yaml
cd ./ghcr.io && flux push artifact \
oci://ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \
--path="./flux-system" \
--source=${{ github.repositoryUrl }} \
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
- name: Push manifests to DockerHub
run: |
mkdir -p ./docker.io/flux-system
flux install --registry=docker.io/fluxcd \
--components-extra=image-reflector-controller,image-automation-controller \
--export > ./docker.io/flux-system/gotk-components.yaml
cd ./docker.io && flux push artifact \
oci://docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \
--path="./flux-system" \
--source=${{ github.repositoryUrl }} \
--revision="${{ github.ref_name }}@sha1:${{ github.sha }}"
- uses: sigstore/cosign-installer@dd6b2e2b610a11fd73dd187a43d57cc1394e35f9 # v3.0.5
- name: Sign manifests
env:
COSIGN_EXPERIMENTAL: 1
run: |
cosign sign --yes ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }}
cosign sign --yes docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }}
- name: Tag manifests
run: |
flux tag artifact oci://ghcr.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \
--tag latest
flux tag artifact oci://docker.io/fluxcd/flux-manifests:${{ steps.prep.outputs.version }} \
--tag latest

View File

@@ -1,7 +1,6 @@
name: scan name: scan
on: on:
workflow_dispatch:
push: push:
branches: [ main ] branches: [ main ]
pull_request: pull_request:
@@ -10,73 +9,60 @@ on:
- cron: '18 10 * * 3' - cron: '18 10 * * 3'
permissions: permissions:
contents: read contents: read # for actions/checkout to fetch code
security-events: write # for codeQL to write security events
jobs: jobs:
scan-fossa: fossa:
name: FOSSA
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.actor != 'dependabot[bot]'
steps: steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - uses: actions/checkout@v3
- name: Run FOSSA scan and upload build data - name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@6728dc6fe9a068c648d080c33829ffbe56565023 # v2.0.0 uses: fossa-contrib/fossa-action@v1
with: with:
# FOSSA Push-Only API Token # FOSSA Push-Only API Token
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
github-token: ${{ github.token }} github-token: ${{ github.token }}
scan-snyk: snyk:
name: Snyk
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
security-events: write
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
steps: steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - uses: actions/checkout@v3
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main uses: fluxcd/pkg//actions/kustomize@main
- name: Setup Go - name: Build manifests
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
with:
go-version: 1.20.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Download modules and build manifests
run: | run: |
make tidy
make cmd/flux/.manifests.done make cmd/flux/.manifests.done
- uses: snyk/actions/setup@b98d498629f1c368650224d6d212bf7dfa89e4bf - name: Run Snyk to check for vulnerabilities
- name: Run Snyk to check for vulnerabilities uses: snyk/actions/golang@master
continue-on-error: true continue-on-error: true
run: |
snyk test --sarif-file-output=snyk.sarif
env: env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --sarif-file-output=snyk.sarif
- name: Upload result to GitHub Code Scanning - name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@0225834cc549ee0ca93cb085b92954821a145866 # v2.3.5 uses: github/codeql-action/upload-sarif@v1
with: with:
sarif_file: snyk.sarif sarif_file: snyk.sarif
scan-codeql: codeql:
name: CodeQL
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
security-events: write
if: github.actor != 'dependabot[bot]'
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 uses: actions/checkout@v3
- name: Setup Go - name: Set up Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 uses: actions/setup-go@v2
with: with:
go-version: 1.20.x go-version: 1.19.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@0225834cc549ee0ca93cb085b92954821a145866 # v2.3.5 uses: github/codeql-action/init@v2
with: with:
languages: go languages: go
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@0225834cc549ee0ca93cb085b92954821a145866 # v2.3.5 uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0225834cc549ee0ca93cb085b92954821a145866 # v2.3.5 uses: github/codeql-action/analyze@v2

View File

@@ -1,4 +1,4 @@
name: update name: Update Components
on: on:
workflow_dispatch: workflow_dispatch:
@@ -7,29 +7,20 @@ on:
push: push:
branches: [main] branches: [main]
permissions:
contents: read
jobs: jobs:
update-components: update-components:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 uses: actions/checkout@v3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 uses: actions/setup-go@v3
with: with:
go-version: 1.20.x go-version: 1.19.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Update component versions - name: Update component versions
id: update id: update
run: | run: |
PR_BODY=$(mktemp) PR_BODY=""
bump_version() { bump_version() {
local LATEST_VERSION=$(curl -s https://api.github.com/repos/fluxcd/$1/releases | jq -r 'sort_by(.published_at) | .[-1] | .tag_name') local LATEST_VERSION=$(curl -s https://api.github.com/repos/fluxcd/$1/releases | jq -r 'sort_by(.published_at) | .[-1] | .tag_name')
@@ -56,8 +47,7 @@ jobs:
fi fi
if [[ "$changed" == true ]]; then if [[ "$changed" == true ]]; then
echo "- $1 to ${LATEST_VERSION}" >> $PR_BODY PR_BODY="$PR_BODY- $1 to ${LATEST_VERSION}%0A https://github.com/fluxcd/$1/blob/${LATEST_VERSION}/CHANGELOG.md%0A"
echo " https://github.com/fluxcd/$1/blob/${LATEST_VERSION}/CHANGELOG.md" >> $PR_BODY
fi fi
} }
@@ -74,17 +64,12 @@ jobs:
git diff git diff
# export PR_BODY for PR and commit # export PR_BODY for PR and commit
# NB: this may look strange but it is the way it should be done to echo "::set-output name=pr_body::$PR_BODY"
# maintain our precious newlines
# Ref: https://github.com/github/docs/issues/21529
echo 'pr_body<<EOF' >> $GITHUB_OUTPUT
cat $PR_BODY >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
} }
- name: Create Pull Request - name: Create Pull Request
id: cpr id: cpr
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 # v5.0.1 uses: peter-evans/create-pull-request@v3
with: with:
token: ${{ secrets.BOT_GITHUB_TOKEN }} token: ${{ secrets.BOT_GITHUB_TOKEN }}
commit-message: | commit-message: |

View File

@@ -65,7 +65,6 @@ signs:
certificate: '${artifact}.pem' certificate: '${artifact}.pem'
args: args:
- sign-blob - sign-blob
- "--yes"
- '--output-certificate=${certificate}' - '--output-certificate=${certificate}'
- '--output-signature=${signature}' - '--output-signature=${signature}'
- '${artifact}' - '${artifact}'
@@ -176,7 +175,6 @@ docker_signs:
- COSIGN_EXPERIMENTAL=1 - COSIGN_EXPERIMENTAL=1
args: args:
- sign - sign
- "--yes"
- '${artifact}' - '${artifact}'
artifacts: all artifacts: all
output: true output: true

View File

@@ -67,7 +67,7 @@ for source changes.
Prerequisites: Prerequisites:
* go >= 1.20 * go >= 1.19
* kubectl >= 1.20 * kubectl >= 1.20
* kustomize >= 4.4 * kustomize >= 4.4
* coreutils (on Mac OS) * coreutils (on Mac OS)

View File

@@ -1,15 +1,19 @@
FROM alpine:3.18 as builder FROM alpine:3.16 as builder
RUN apk add --no-cache ca-certificates curl RUN apk add --no-cache ca-certificates curl
ARG ARCH=linux/amd64 ARG ARCH=linux/amd64
ARG KUBECTL_VER=1.27.2 ARG KUBECTL_VER=1.25.0
RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \ RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \ -o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \
kubectl version --client=true kubectl version --client=true
FROM alpine:3.18 as flux-cli FROM alpine:3.16 as flux-cli
# Create minimal nsswitch.conf file to prioritize the usage of /etc/hosts over DNS queries.
# https://github.com/gliderlabs/docker-alpine/issues/367#issuecomment-354316460
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf
RUN apk add --no-cache ca-certificates RUN apk add --no-cache ca-certificates

View File

@@ -17,8 +17,8 @@ rwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2
all: test build all: test build
tidy: tidy:
go mod tidy -compat=1.20 go mod tidy -compat=1.19
cd tests/azure && go mod tidy -compat=1.20 cd tests/azure && go mod tidy -compat=1.19
fmt: fmt:
go fmt ./... go fmt ./...

View File

@@ -1,14 +1,14 @@
# Flux version 2 # Flux version 2
[![release](https://img.shields.io/github/release/fluxcd/flux2/all.svg)](https://github.com/fluxcd/flux2/releases)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4782/badge)](https://bestpractices.coreinfrastructure.org/projects/4782) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4782/badge)](https://bestpractices.coreinfrastructure.org/projects/4782)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2/badge)](https://api.securityscorecards.dev/projects/github.com/fluxcd/flux2) [![e2e](https://github.com/fluxcd/flux2/workflows/e2e/badge.svg)](https://github.com/fluxcd/flux2/actions)
[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Ffluxcd%2Fflux2?ref=badge_shield) [![report](https://goreportcard.com/badge/github.com/fluxcd/flux2)](https://goreportcard.com/report/github.com/fluxcd/flux2)
[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/flux2)](https://artifacthub.io/packages/helm/fluxcd-community/flux2) [![license](https://img.shields.io/github/license/fluxcd/flux2.svg)](https://github.com/fluxcd/flux2/blob/main/LICENSE)
[![release](https://img.shields.io/github/release/fluxcd/flux2/all.svg)](https://github.com/fluxcd/flux2/releases)
Flux is a tool for keeping Kubernetes clusters in sync with sources of Flux is a tool for keeping Kubernetes clusters in sync with sources of
configuration (like Git repositories and OCI artifacts), configuration (like Git repositories), and automating updates to
and automating updates to configuration when there is new code to deploy. configuration when there is new code to deploy.
Flux version 2 ("v2") is built from the ground up to use Kubernetes' Flux version 2 ("v2") is built from the ground up to use Kubernetes'
API extension system, and to integrate with Prometheus and other core API extension system, and to integrate with Prometheus and other core
@@ -20,8 +20,7 @@ Flux v2 is constructed with the [GitOps Toolkit](#gitops-toolkit), a
set of composable APIs and specialized tools for building Continuous set of composable APIs and specialized tools for building Continuous
Delivery on top of Kubernetes. Delivery on top of Kubernetes.
Flux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) project, used in Flux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) project.
production by various [organisations](https://fluxcd.io/adopters) and [cloud providers](https://fluxcd.io/ecosystem).
## Quickstart and documentation ## Quickstart and documentation
@@ -78,17 +77,16 @@ new contributors and there are a multitude of ways to get involved.
- Getting Started? - Getting Started?
- Look at our [Get Started guide](https://fluxcd.io/flux/get-started/) and give us feedback - Look at our [Get Started guide](https://fluxcd.io/flux/get-started/) and give us feedback
- Need help? - Need help?
- First: Ask questions on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions). - 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/). - Second: Talk to us in the #flux channel on [CNCF Slack](https://slack.cncf.io/)
- Please follow our [Support Guidelines](https://fluxcd.io/support/) - Please follow our [Support Guidelines](https://fluxcd.io/support/)
(in short: be nice, be respectful of volunteers' time, understand that maintainers and (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). 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? - Have feature proposals or want to contribute?
- Propose features on our [GitHub Discussions page](https://github.com/fluxcd/flux2/discussions). - 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)). - Join our upcoming dev meetings ([meeting access and agenda](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view))
- [Join the flux-dev mailing list](https://lists.cncf.io/g/cncf-flux-dev). - [Join the flux-dev mailing list](https://lists.cncf.io/g/cncf-flux-dev).
- Check out [how to contribute](CONTRIBUTING.md) to the project. - Check out [how to contribute](CONTRIBUTING.md) to the project
- Check out the [project roadmap](https://fluxcd.io/roadmap/).
### Events ### Events

View File

@@ -35,20 +35,6 @@ You can download a specific version with:
version: 0.32.0 version: 0.32.0
``` ```
You can also authenticate against the GitHub API using GitHub Actions' `GITHUB_TOKEN` secret.
For more information, please [read about the GitHub token secret](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret).
```yaml
steps:
- name: Setup Flux CLI
uses: fluxcd/flux2/action@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
```
This is useful if you are seeing failures on shared runners, those failures are usually API limits being hit.
### Automate Flux updates ### Automate Flux updates
Example workflow for updating Flux's components generated with `flux bootstrap --path=clusters/production`: Example workflow for updating Flux's components generated with `flux bootstrap --path=clusters/production`:
@@ -61,16 +47,12 @@ on:
schedule: schedule:
- cron: "0 * * * *" - cron: "0 * * * *"
permissions:
contents: write
pull-requests: write
jobs: jobs:
components: components:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Setup Flux CLI - name: Setup Flux CLI
uses: fluxcd/flux2/action@main uses: fluxcd/flux2/action@main
- name: Check for updates - name: Check for updates
@@ -80,9 +62,9 @@ jobs:
--export > ./clusters/production/flux-system/gotk-components.yaml --export > ./clusters/production/flux-system/gotk-components.yaml
VERSION="$(flux -v)" VERSION="$(flux -v)"
echo "flux_version=$VERSION" >> $GITHUB_OUTPUT echo "::set-output name=flux_version::$VERSION"
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@v4 uses: peter-evans/create-pull-request@v3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
branch: update-flux branch: update-flux
@@ -132,31 +114,24 @@ jobs:
flux push artifact $OCI_REPO:$(git rev-parse --short HEAD) \ flux push artifact $OCI_REPO:$(git rev-parse --short HEAD) \
--path="./deploy" \ --path="./deploy" \
--source="$(git config --get remote.origin.url)" \ --source="$(git config --get remote.origin.url)" \
--revision="$(git branch --show-current)@sha1:$(git rev-parse HEAD)" --revision="$(git branch --show-current)/$(git rev-parse HEAD)"
- name: Deploy manifests to staging - name: Deploy manifests to staging
run: | run: |
flux tag artifact $OCI_REPO:$(git rev-parse --short HEAD) --tag staging flux tag artifact $OCI_REPO:$(git rev-parse --short HEAD) --tag staging
``` ```
### Push and sign Kubernetes manifests to container registries Example workflow for publishing Kubernetes manifests bundled as OCI artifacts to Docker Hub:
Example workflow for publishing Kubernetes manifests bundled as OCI artifacts
which are signed with Cosign and GitHub OIDC:
```yaml ```yaml
name: push-sign-artifact name: push-artifact-production
on: on:
push: push:
branches: tags:
- 'main' - '*'
permissions:
packages: write # needed for ghcr.io access
id-token: write # needed for keyless signing
env: env:
OCI_REPO: "oci://ghcr.io/my-org/manifests/${{ github.event.repository.name }}" OCI_REPO: "oci://docker.io/my-org/app-config"
jobs: jobs:
kubernetes: kubernetes:
@@ -166,24 +141,23 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Flux CLI - name: Setup Flux CLI
uses: fluxcd/flux2/action@main uses: fluxcd/flux2/action@main
- name: Setup Cosign - name: Login to Docker Hub
uses: sigstore/cosign-installer@main
- name: Login to GHCR
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ghcr.io username: ${{ secrets.DOCKER_USERNAME }}
username: ${{ github.actor }} password: ${{ secrets.DOCKER_PASSWORD }}
password: ${{ secrets.GITHUB_TOKEN }} - name: Generate manifests
- name: Push and sign manifests
run: | run: |
digest_url=$(flux push artifact \ kustomize build ./manifests/production > ./deploy/app.yaml
$OCI_REPO:$(git rev-parse --short HEAD) \ - name: Push manifests
--path="./manifests" \ run: |
--source="$(git config --get remote.origin.url)" \ flux push artifact $OCI_REPO:$(git tag --points-at HEAD) \
--revision="$(git branch --show-current)@sha1:$(git rev-parse HEAD)" |\ --path="./deploy" \
jq -r '. | .repository + "@" + .digest') --source="$(git config --get remote.origin.url)" \
--revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)"
cosign sign $digest_url - name: Deploy manifests to production
run: |
flux tag artifact $OCI_REPO:$(git tag --points-at HEAD) --tag production
``` ```
### End-to-end testing ### End-to-end testing
@@ -203,7 +177,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Setup Flux CLI - name: Setup Flux CLI
uses: fluxcd/flux2/action@main uses: fluxcd/flux2/action@main
- name: Setup Kubernetes Kind - name: Setup Kubernetes Kind

View File

@@ -15,9 +15,6 @@ inputs:
bindir: bindir:
description: "Optional location of the Flux binary. Will not use sudo if set. Updates System Path." description: "Optional location of the Flux binary. Will not use sudo if set. Updates System Path."
required: false required: false
token:
description: "GitHub Token used to authentication against the API (generally only needed to prevent quota limit errors)"
required: false
runs: runs:
using: composite using: composite
steps: steps:
@@ -26,29 +23,20 @@ runs:
run: | run: |
ARCH=${{ inputs.arch }} ARCH=${{ inputs.arch }}
VERSION=${{ inputs.version }} VERSION=${{ inputs.version }}
TOKEN=${{ inputs.token }}
if [ -z "${VERSION}" ]; then if [ -z $VERSION ]; then
if [ -n "${TOKEN}" ]; then VERSION=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest -sL | grep tag_name | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-)
VERSION_SLUG=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest --silent --location --header "Authorization: token ${TOKEN}" | grep tag_name)
else
# With no GITHUB_TOKEN you will experience occasional failures due to rate limiting
# Ref: https://github.com/fluxcd/flux2/issues/3509#issuecomment-1400820992
VERSION_SLUG=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest --silent --location | grep tag_name)
fi
VERSION=$(echo "${VERSION_SLUG}" | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-)
fi fi
BIN_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_${ARCH}.tar.gz" BIN_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_${ARCH}.tar.gz"
curl --silent --fail --location "${BIN_URL}" --output /tmp/flux.tar.gz curl -sL ${BIN_URL} -o /tmp/flux.tar.gz
mkdir -p /tmp/flux mkdir -p /tmp/flux
tar -C /tmp/flux/ -zxvf /tmp/flux.tar.gz tar -C /tmp/flux/ -zxvf /tmp/flux.tar.gz
- name: "Copy Flux binary to execute location" - name: "Copy Flux binary to execute location"
shell: bash shell: bash
run: | run: |
BINDIR=${{ inputs.bindir }} BINDIR=${{ inputs.bindir }}
if [ -z "${BINDIR}" ]; then if [ -z $BINDIR ]; then
sudo cp /tmp/flux/flux /usr/local/bin sudo cp /tmp/flux/flux /usr/local/bin
else else
cp /tmp/flux/flux "${BINDIR}" cp /tmp/flux/flux "${BINDIR}"

View File

@@ -19,7 +19,7 @@ package main
import ( import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
// notificationv1.Alert // notificationv1.Alert

View File

@@ -19,7 +19,7 @@ package main
import ( import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
// notificationv1.Provider // notificationv1.Provider

View File

@@ -23,21 +23,21 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen" "github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
) )
var bootstrapCmd = &cobra.Command{ var bootstrapCmd = &cobra.Command{
Use: "bootstrap", Use: "bootstrap",
Short: "Deploy Flux on a cluster the GitOps way.", Short: "Bootstrap toolkit components",
Long: `The bootstrap sub-commands push the Flux manifests to a Git repository Long: "The bootstrap sub-commands bootstrap the toolkit components on the targeted Git provider.",
and deploy Flux on the cluster.`,
} }
type bootstrapFlags struct { type bootstrapFlags struct {
version string version string
arch flags.Arch
logLevel flags.LogLevel logLevel flags.LogLevel
branch string branch string
@@ -91,9 +91,9 @@ func init() {
"list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller'") "list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller'")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd", bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd",
"container registry where the Flux controller images are published") "container registry where the toolkit images are published")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.imagePullSecret, "image-pull-secret", "", bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.imagePullSecret, "image-pull-secret", "",
"Kubernetes secret name used for pulling the controller images from a private registry") "Kubernetes secret name used for pulling the toolkit images from a private registry")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.branch, "branch", bootstrapDefaultBranch, "Git branch") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.branch, "branch", bootstrapDefaultBranch, "Git branch")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.recurseSubmodules, "recurse-submodules", false, bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.recurseSubmodules, "recurse-submodules", false,
@@ -102,15 +102,15 @@ func init() {
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.manifestsPath, "manifests", "", "path to the manifest directory") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.manifestsPath, "manifests", "", "path to the manifest directory")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.watchAllNamespaces, "watch-all-namespaces", true, bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.watchAllNamespaces, "watch-all-namespaces", true,
"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the Flux controllers are installed") "watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.networkPolicy, "network-policy", true, bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.networkPolicy, "network-policy", true,
"setup Kubernetes network policies to deny ingress access to the Flux controllers from other namespaces") "deny ingress access to the toolkit controllers from other namespaces using network policies")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.tokenAuth, "token-auth", false, bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.tokenAuth, "token-auth", false,
"when enabled, the personal access token will be used instead of the SSH deploy key") "when enabled, the personal access token will be used instead of SSH deploy key")
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.logLevel, "log-level", bootstrapArgs.logLevel.Description()) bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.logLevel, "log-level", bootstrapArgs.logLevel.Description())
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.tolerationKeys, "toleration-keys", nil, bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.tolerationKeys, "toleration-keys", nil,
"list of toleration keys used to schedule the controller pods onto nodes with matching taints") "list of toleration keys used to schedule the components pods onto nodes with matching taints")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.secretName, "secret-name", rootArgs.defaults.Namespace, "name of the secret the sync credentials can be found in or stored to") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.secretName, "secret-name", rootArgs.defaults.Namespace, "name of the secret the sync credentials can be found in or stored to")
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyAlgorithm, "ssh-key-algorithm", bootstrapArgs.keyAlgorithm.Description()) bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyAlgorithm, "ssh-key-algorithm", bootstrapArgs.keyAlgorithm.Description())
@@ -129,6 +129,8 @@ func init() {
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'")
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.arch, "arch", bootstrapArgs.arch.Description())
bootstrapCmd.PersistentFlags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64")
bootstrapCmd.PersistentFlags().MarkHidden("manifests") bootstrapCmd.PersistentFlags().MarkHidden("manifests")
rootCmd.AddCommand(bootstrapCmd) rootCmd.AddCommand(bootstrapCmd)

View File

@@ -22,42 +22,45 @@ import (
"os" "os"
"time" "time"
"github.com/fluxcd/pkg/git" "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/fluxcd/pkg/git/gogit"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/internal/bootstrap"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/v2/pkg/bootstrap" "github.com/fluxcd/flux2/internal/bootstrap/provider"
"github.com/fluxcd/flux2/v2/pkg/bootstrap/provider" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/v2/pkg/manifestgen" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
) )
var bootstrapBServerCmd = &cobra.Command{ var bootstrapBServerCmd = &cobra.Command{
Use: "bitbucket-server", Use: "bitbucket-server",
Short: "Deploy Flux on a cluster connected to a Bitbucket Server repository", Short: "Bootstrap toolkit components in a Bitbucket Server repository",
Long: `The bootstrap bitbucket-server command creates the Bitbucket Server repository if it doesn't exists and Long: `The bootstrap bitbucket-server command creates the Bitbucket Server repository if it doesn't exists and
commits the Flux manifests to the master branch. commits the toolkit components manifests to the master branch.
Then it configures the target cluster to synchronize with the repository. Then it configures the target cluster to synchronize with the repository.
If the Flux components are present on the cluster, If the toolkit components are present on the cluster,
the bootstrap command will perform an upgrade if needed.`, the bootstrap command will perform an upgrade if needed.`,
Example: ` # Create a Bitbucket Server API token and export it as an env var Example: ` # Create a Bitbucket Server API token and export it as an env var
export BITBUCKET_TOKEN=<my-token> export BITBUCKET_TOKEN=<my-token>
# Run bootstrap for a private repository using HTTPS token authentication # Run bootstrap for a private repository using HTTPS token authentication
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --hostname=<domain> --token-auth --path=clusters/my-cluster flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --hostname=<domain> --token-auth
# Run bootstrap for a private repository using SSH authentication # Run bootstrap for a private repository using SSH authentication
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --hostname=<domain> --path=clusters/my-cluster flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --hostname=<domain>
# Run bootstrap for a repository path
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --path=dev-cluster --hostname=<domain>
# Run bootstrap for a public repository on a personal account # Run bootstrap for a public repository on a personal account
flux bootstrap bitbucket-server --owner=<user> --repository=<repository name> --private=false --personal --hostname=<domain> --token-auth --path=clusters/my-cluster flux bootstrap bitbucket-server --owner=<user> --repository=<repository name> --private=false --personal --hostname=<domain> --token-auth
# Run bootstrap for a an existing repository with a branch named main # Run bootstrap for a an existing repository with a branch named main
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --branch=main --hostname=<domain> --token-auth --path=clusters/my-cluster`, flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --branch=main --hostname=<domain> --token-auth`,
RunE: bootstrapBServerCmdRun, RunE: bootstrapBServerCmdRun,
} }
@@ -125,9 +128,7 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
} }
// Manifest base // Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil { if ver, err := getVersion(bootstrapArgs.version); err == nil {
return err
} else {
bootstrapArgs.version = ver bootstrapArgs.version = ver
} }
manifestsBase, err := buildEmbeddedManifestBase() manifestsBase, err := buildEmbeddedManifestBase()
@@ -170,17 +171,10 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to create temporary working dir: %w", err) return fmt.Errorf("failed to create temporary working dir: %w", err)
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
gitClient := gogit.New(tmpDir, &http.BasicAuth{
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()} Username: user,
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{ Password: bitbucketToken,
Transport: git.HTTPS, })
Username: user,
Password: bitbucketToken,
CAFile: caBundle,
}, clientOpts...)
if err != nil {
return fmt.Errorf("failed to create a Git client: %w", err)
}
// Install manifest config // Install manifest config
installOptions := install.Options{ installOptions := install.Options{
@@ -218,18 +212,19 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
secretOpts.Username = bServerArgs.username secretOpts.Username = bServerArgs.username
} }
secretOpts.Password = bitbucketToken secretOpts.Password = bitbucketToken
secretOpts.CAFile = caBundle
} else { if bootstrapArgs.caFile != "" {
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password) secretOpts.CAFilePath = bootstrapArgs.caFile
if err != nil {
return err
} }
secretOpts.Keypair = keypair } else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm) secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits) secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
secretOpts.SSHHostname = bServerArgs.hostname secretOpts.SSHHostname = bServerArgs.hostname
if bootstrapArgs.privateKeyFile != "" {
secretOpts.PrivateKeyPath = bootstrapArgs.privateKeyFile
}
if bootstrapArgs.sshHostname != "" { if bootstrapArgs.sshHostname != "" {
secretOpts.SSHHostname = bootstrapArgs.sshHostname secretOpts.SSHHostname = bootstrapArgs.sshHostname
} }
@@ -244,27 +239,23 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
Secret: bootstrapArgs.secretName, Secret: bootstrapArgs.secretName,
TargetPath: bServerArgs.path.ToSlash(), TargetPath: bServerArgs.path.ToSlash(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile, ManifestFile: sync.MakeDefaultOptions().ManifestFile,
GitImplementation: sourceGitArgs.gitImplementation.String(),
RecurseSubmodules: bootstrapArgs.recurseSubmodules, RecurseSubmodules: bootstrapArgs.recurseSubmodules,
} }
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
if err != nil {
return err
}
// Bootstrap config // Bootstrap config
bootstrapOpts := []bootstrap.GitProviderOption{ bootstrapOpts := []bootstrap.GitProviderOption{
bootstrap.WithProviderRepository(bServerArgs.owner, bServerArgs.repository, bServerArgs.personal), bootstrap.WithProviderRepository(bServerArgs.owner, bServerArgs.repository, bServerArgs.personal),
bootstrap.WithBranch(bootstrapArgs.branch), bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithBootstrapTransportType("https"), bootstrap.WithBootstrapTransportType("https"),
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail), bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix), bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithProviderTeamPermissions(mapTeamSlice(bServerArgs.teams, bServerDefaultPermission)), bootstrap.WithProviderTeamPermissions(mapTeamSlice(bServerArgs.teams, bServerDefaultPermission)),
bootstrap.WithReadWriteKeyPermissions(bServerArgs.readWriteKey), bootstrap.WithReadWriteKeyPermissions(bServerArgs.readWriteKey),
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions), bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
bootstrap.WithLogger(logger), bootstrap.WithLogger(logger),
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), bootstrap.WithCABundle(caBundle),
bootstrap.WithGitCommitSigning(bootstrapArgs.gpgKeyRingPath, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
} }
if bootstrapArgs.sshHostname != "" { if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))

View File

@@ -24,48 +24,44 @@ import (
"strings" "strings"
"time" "time"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/manifoldco/promptui" "github.com/manifoldco/promptui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/internal/bootstrap"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/v2/pkg/bootstrap" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/v2/pkg/manifestgen" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/pkg/git" "github.com/fluxcd/flux2/pkg/manifestgen/sync"
"github.com/fluxcd/pkg/git/gogit"
) )
var bootstrapGitCmd = &cobra.Command{ var bootstrapGitCmd = &cobra.Command{
Use: "git", Use: "git",
Short: "Deploy Flux on a cluster connected to a Git repository", Short: "Bootstrap toolkit components in a Git repository",
Long: `The bootstrap git command commits the Flux manifests to the Long: `The bootstrap git command commits the toolkit components manifests to the
branch of a Git repository. And then it configures the target cluster to synchronize with branch of a Git repository. It then configures the target cluster to synchronize with
that repository. If the Flux components are present on the cluster, the bootstrap the repository. If the toolkit components are present on the cluster, the bootstrap
command will perform an upgrade if needed.`, command will perform an upgrade if needed.`,
Example: ` # Run bootstrap for a Git repository and authenticate with your SSH agent Example: ` # Run bootstrap for a Git repository and authenticate with your SSH agent
flux bootstrap git --url=ssh://git@example.com/repository.git --path=clusters/my-cluster flux bootstrap git --url=ssh://git@example.com/repository.git
# Run bootstrap for a Git repository and authenticate using a password # Run bootstrap for a Git repository and authenticate using a password
flux bootstrap git --url=https://example.com/repository.git --password=<password> --path=clusters/my-cluster flux bootstrap git --url=https://example.com/repository.git --password=<password>
# Run bootstrap for a Git repository and authenticate using a password from environment variable # Run bootstrap for a Git repository and authenticate using a password from environment variable
GIT_PASSWORD=<password> && flux bootstrap git --url=https://example.com/repository.git --path=clusters/my-cluster GIT_PASSWORD=<password> && flux bootstrap git --url=https://example.com/repository.git
# Run bootstrap for a Git repository with a passwordless private key # Run bootstrap for a Git repository with a passwordless private key
flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key> --path=clusters/my-cluster flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key>
# Run bootstrap for a Git repository with a private key and password # Run bootstrap for a Git repository with a private key and password
flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key> --password=<password> --path=clusters/my-cluster flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key> --password=<password>
# Run bootstrap for a Git repository on AWS CodeCommit
flux bootstrap git --url=ssh://<SSH-Key-ID>@git-codecommit.<region>.amazonaws.com/v1/repos/<repository> --private-key-file=<path/to/private.key> --password=<SSH-passphrase> --path=clusters/my-cluster
# Run bootstrap for a Git repository on Azure Devops
flux bootstrap git --url=ssh://git@ssh.dev.azure.com/v3/<org>/<project>/<repository> --ssh-key-algorithm=rsa --ssh-rsa-bits=4096 --path=clusters/my-cluster
`, `,
RunE: bootstrapGitCmdRun, RunE: bootstrapGitCmdRun,
} }
@@ -93,7 +89,7 @@ func init() {
bootstrapGitCmd.Flags().StringVarP(&gitArgs.username, "username", "u", "git", "basic authentication username") bootstrapGitCmd.Flags().StringVarP(&gitArgs.username, "username", "u", "git", "basic authentication username")
bootstrapGitCmd.Flags().StringVarP(&gitArgs.password, "password", "p", "", "basic authentication password") bootstrapGitCmd.Flags().StringVarP(&gitArgs.password, "password", "p", "", "basic authentication password")
bootstrapGitCmd.Flags().BoolVarP(&gitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation") bootstrapGitCmd.Flags().BoolVarP(&gitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
bootstrapGitCmd.Flags().BoolVar(&gitArgs.insecureHttpAllowed, "allow-insecure-http", false, "allows insecure HTTP connections") bootstrapGitCmd.Flags().BoolVar(&gitArgs.insecureHttpAllowed, "allow-insecure-http", false, "allows http git url connections")
bootstrapCmd.AddCommand(bootstrapGitCmd) bootstrapCmd.AddCommand(bootstrapGitCmd)
} }
@@ -120,22 +116,9 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
gitAuth, err := transportForURL(repositoryURL)
if strings.Contains(repositoryURL.Hostname(), "git-codecommit") && strings.Contains(repositoryURL.Hostname(), "amazonaws.com") { if err != nil {
if repositoryURL.Scheme == string(git.SSH) { return err
if repositoryURL.User == nil {
return fmt.Errorf("invalid AWS CodeCommit url: ssh username should be specified in the url")
}
if repositoryURL.User.Username() == git.DefaultPublicKeyAuthUser {
return fmt.Errorf("invalid AWS CodeCommit url: ssh username should be the SSH key ID for the provided private key")
}
if bootstrapArgs.privateKeyFile == "" {
return fmt.Errorf("private key file is required for bootstrapping against AWS CodeCommit using ssh")
}
}
if repositoryURL.Scheme == string(git.HTTPS) && !bootstrapArgs.tokenAuth {
return fmt.Errorf("--token-auth=true must be specified for using an HTTPS AWS CodeCommit url")
}
} }
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
@@ -147,9 +130,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
} }
// Manifest base // Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil { if ver, err := getVersion(bootstrapArgs.version); err == nil {
return err
} else {
bootstrapArgs.version = ver bootstrapArgs.version = ver
} }
manifestsBase, err := buildEmbeddedManifestBase() manifestsBase, err := buildEmbeddedManifestBase()
@@ -164,28 +145,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to create temporary working dir: %w", err) return fmt.Errorf("failed to create temporary working dir: %w", err)
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
gitClient := gogit.New(tmpDir, gitAuth)
var caBundle []byte
if bootstrapArgs.caFile != "" {
var err error
caBundle, err = os.ReadFile(bootstrapArgs.caFile)
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
}
authOpts, err := getAuthOpts(repositoryURL, caBundle)
if err != nil {
return fmt.Errorf("failed to create authentication options for %s: %w", repositoryURL.String(), err)
}
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}
if gitArgs.insecureHttpAllowed {
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
}
gitClient, err := gogit.NewClient(tmpDir, authOpts, clientOpts...)
if err != nil {
return fmt.Errorf("failed to create a Git client: %w", err)
}
// Install manifest config // Install manifest config
installOptions := install.Options{ installOptions := install.Options{
@@ -219,7 +179,10 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
if bootstrapArgs.tokenAuth { if bootstrapArgs.tokenAuth {
secretOpts.Username = gitArgs.username secretOpts.Username = gitArgs.username
secretOpts.Password = gitArgs.password secretOpts.Password = gitArgs.password
secretOpts.CAFile = caBundle
if bootstrapArgs.caFile != "" {
secretOpts.CAFilePath = bootstrapArgs.caFile
}
// Remove port of the given host when not syncing over HTTP/S to not assume port for protocol // Remove port of the given host when not syncing over HTTP/S to not assume port for protocol
// This _might_ be overwritten later on by e.g. --ssh-hostname // This _might_ be overwritten later on by e.g. --ssh-hostname
@@ -250,12 +213,9 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
if bootstrapArgs.sshHostname != "" { if bootstrapArgs.sshHostname != "" {
repositoryURL.Host = bootstrapArgs.sshHostname repositoryURL.Host = bootstrapArgs.sshHostname
} }
if bootstrapArgs.privateKeyFile != "" {
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password) secretOpts.PrivateKeyPath = bootstrapArgs.privateKeyFile
if err != nil {
return err
} }
secretOpts.Keypair = keypair
// Configure last as it depends on the config above. // Configure last as it depends on the config above.
secretOpts.SSHHostname = repositoryURL.Host secretOpts.SSHHostname = repositoryURL.Host
@@ -271,24 +231,30 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
Secret: bootstrapArgs.secretName, Secret: bootstrapArgs.secretName,
TargetPath: gitArgs.path.ToSlash(), TargetPath: gitArgs.path.ToSlash(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile, ManifestFile: sync.MakeDefaultOptions().ManifestFile,
GitImplementation: sourceGitArgs.gitImplementation.String(),
RecurseSubmodules: bootstrapArgs.recurseSubmodules, RecurseSubmodules: bootstrapArgs.recurseSubmodules,
} }
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath) var caBundle []byte
if err != nil { if bootstrapArgs.caFile != "" {
return err var err error
caBundle, err = os.ReadFile(bootstrapArgs.caFile)
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
} }
// Bootstrap config // Bootstrap config
bootstrapOpts := []bootstrap.GitOption{ bootstrapOpts := []bootstrap.GitOption{
bootstrap.WithRepositoryURL(gitArgs.url), bootstrap.WithRepositoryURL(gitArgs.url),
bootstrap.WithBranch(bootstrapArgs.branch), bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail), bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix), bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions), bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
bootstrap.WithPostGenerateSecretFunc(promptPublicKey), bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
bootstrap.WithLogger(logger), bootstrap.WithLogger(logger),
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), bootstrap.WithCABundle(caBundle),
bootstrap.WithGitCommitSigning(bootstrapArgs.gpgKeyRingPath, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
} }
// Setup bootstrapper with constructed configs // Setup bootstrapper with constructed configs
@@ -301,47 +267,30 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout) return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
} }
// getAuthOpts retruns a AuthOptions based on the scheme // transportForURL constructs a transport.AuthMethod based on the scheme
// of the given URL and the configured flags. If the protocol equals // of the given URL and the configured flags. If the protocol equals
// "ssh" but no private key is configured, authentication using the local // "ssh" but no private key is configured, authentication using the local
// SSH-agent is attempted. // SSH-agent is attempted.
func getAuthOpts(u *url.URL, caBundle []byte) (*git.AuthOptions, error) { func transportForURL(u *url.URL) (transport.AuthMethod, error) {
switch u.Scheme { switch u.Scheme {
case "http": case "http":
if !gitArgs.insecureHttpAllowed { if !gitArgs.insecureHttpAllowed {
return nil, fmt.Errorf("scheme http is insecure, pass --allow-insecure-http=true to allow it") return nil, fmt.Errorf("scheme http is insecure, pass --allow-insecure-http=true to allow it")
} }
return &git.AuthOptions{ return &http.BasicAuth{
Transport: git.HTTP, Username: gitArgs.username,
Username: gitArgs.username, Password: gitArgs.password,
Password: gitArgs.password,
}, nil }, nil
case "https": case "https":
return &git.AuthOptions{ return &http.BasicAuth{
Transport: git.HTTPS, Username: gitArgs.username,
Username: gitArgs.username, Password: gitArgs.password,
Password: gitArgs.password,
CAFile: caBundle,
}, nil }, nil
case "ssh": case "ssh":
authOpts := &git.AuthOptions{
Transport: git.SSH,
Username: u.User.Username(),
Password: gitArgs.password,
}
if bootstrapArgs.privateKeyFile != "" { if bootstrapArgs.privateKeyFile != "" {
pk, err := os.ReadFile(bootstrapArgs.privateKeyFile) return ssh.NewPublicKeysFromFile(u.User.Username(), bootstrapArgs.privateKeyFile, gitArgs.password)
if err != nil {
return nil, err
}
kh, err := sourcesecret.ScanHostKey(u.Host)
if err != nil {
return nil, err
}
authOpts.Identity = pk
authOpts.KnownHosts = kh
} }
return authOpts, nil return nil, nil
default: default:
return nil, fmt.Errorf("scheme %q is not supported", u.Scheme) return nil, fmt.Errorf("scheme %q is not supported", u.Scheme)
} }

View File

@@ -22,51 +22,54 @@ import (
"os" "os"
"time" "time"
"github.com/fluxcd/pkg/git" "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/fluxcd/pkg/git/gogit"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/internal/bootstrap"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/v2/pkg/bootstrap" "github.com/fluxcd/flux2/internal/bootstrap/provider"
"github.com/fluxcd/flux2/v2/pkg/bootstrap/provider" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/v2/pkg/manifestgen" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
) )
var bootstrapGitHubCmd = &cobra.Command{ var bootstrapGitHubCmd = &cobra.Command{
Use: "github", Use: "github",
Short: "Deploy Flux on a cluster connected to a GitHub repository", Short: "Bootstrap toolkit components in a GitHub repository",
Long: `The bootstrap github command creates the GitHub repository if it doesn't exists and Long: `The bootstrap github command creates the GitHub repository if it doesn't exists and
commits the Flux manifests to the specified branch. commits the toolkit components manifests to the main branch.
Then it configures the target cluster to synchronize with that repository. Then it configures the target cluster to synchronize with the repository.
If the Flux components are present on the cluster, If the toolkit components are present on the cluster,
the bootstrap command will perform an upgrade if needed.`, the bootstrap command will perform an upgrade if needed.`,
Example: ` # Create a GitHub personal access token and export it as an env var Example: ` # Create a GitHub personal access token and export it as an env var
export GITHUB_TOKEN=<my-token> export GITHUB_TOKEN=<my-token>
# Run bootstrap for a private repository owned by a GitHub organization # Run bootstrap for a private repository owned by a GitHub organization
flux bootstrap github --owner=<organization> --repository=<repository name> --path=clusters/my-cluster flux bootstrap github --owner=<organization> --repository=<repository name>
# Run bootstrap for a private repository and assign organization teams to it # Run bootstrap for a private repository and assign organization teams to it
flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug> --path=clusters/my-cluster flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug>
# Run bootstrap for a private repository and assign organization teams with their access level(e.g maintain, admin) to it # Run bootstrap for a private repository and assign organization teams with their access level(e.g maintain, admin) to it
flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug>:<access-level> --path=clusters/my-cluster flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug>:<access-level>
# Run bootstrap for a repository path
flux bootstrap github --owner=<organization> --repository=<repository name> --path=dev-cluster
# Run bootstrap for a public repository on a personal account # Run bootstrap for a public repository on a personal account
flux bootstrap github --owner=<user> --repository=<repository name> --private=false --personal=true --path=clusters/my-cluster flux bootstrap github --owner=<user> --repository=<repository name> --private=false --personal=true
# Run bootstrap for a private repository hosted on GitHub Enterprise using SSH auth # Run bootstrap for a private repository hosted on GitHub Enterprise using SSH auth
flux bootstrap github --owner=<organization> --repository=<repository name> --hostname=<domain> --ssh-hostname=<domain> --path=clusters/my-cluster flux bootstrap github --owner=<organization> --repository=<repository name> --hostname=<domain> --ssh-hostname=<domain>
# Run bootstrap for a private repository hosted on GitHub Enterprise using HTTPS auth # Run bootstrap for a private repository hosted on GitHub Enterprise using HTTPS auth
flux bootstrap github --owner=<organization> --repository=<repository name> --hostname=<domain> --token-auth --path=clusters/my-cluster flux bootstrap github --owner=<organization> --repository=<repository name> --hostname=<domain> --token-auth
# Run bootstrap for an existing repository with a branch named main # Run bootstrap for an existing repository with a branch named main
flux bootstrap github --owner=<organization> --repository=<repository name> --branch=main --path=clusters/my-cluster`, flux bootstrap github --owner=<organization> --repository=<repository name> --branch=main`,
RunE: bootstrapGitHubCmdRun, RunE: bootstrapGitHubCmdRun,
} }
@@ -129,9 +132,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
} }
// Manifest base // Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil { if ver, err := getVersion(bootstrapArgs.version); err == nil {
return err
} else {
bootstrapArgs.version = ver bootstrapArgs.version = ver
} }
manifestsBase, err := buildEmbeddedManifestBase() manifestsBase, err := buildEmbeddedManifestBase()
@@ -160,22 +161,16 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
// Lazy go-git repository
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-") tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
if err != nil { if err != nil {
return fmt.Errorf("failed to create temporary working dir: %w", err) return fmt.Errorf("failed to create temporary working dir: %w", err)
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
gitClient := gogit.New(tmpDir, &http.BasicAuth{
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()} Username: githubArgs.owner,
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{ Password: ghToken,
Transport: git.HTTPS, })
Username: githubArgs.owner,
Password: ghToken,
CAFile: caBundle,
}, clientOpts...)
if err != nil {
return fmt.Errorf("failed to create a Git client: %w", err)
}
// Install manifest config // Install manifest config
installOptions := install.Options{ installOptions := install.Options{
@@ -209,13 +204,16 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
if bootstrapArgs.tokenAuth { if bootstrapArgs.tokenAuth {
secretOpts.Username = "git" secretOpts.Username = "git"
secretOpts.Password = ghToken secretOpts.Password = ghToken
secretOpts.CAFile = caBundle
if bootstrapArgs.caFile != "" {
secretOpts.CAFilePath = bootstrapArgs.caFile
}
} else { } else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm) secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits) secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
secretOpts.SSHHostname = githubArgs.hostname secretOpts.SSHHostname = githubArgs.hostname
if bootstrapArgs.sshHostname != "" { if bootstrapArgs.sshHostname != "" {
secretOpts.SSHHostname = bootstrapArgs.sshHostname secretOpts.SSHHostname = bootstrapArgs.sshHostname
} }
@@ -230,26 +228,23 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
Secret: bootstrapArgs.secretName, Secret: bootstrapArgs.secretName,
TargetPath: githubArgs.path.ToSlash(), TargetPath: githubArgs.path.ToSlash(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile, ManifestFile: sync.MakeDefaultOptions().ManifestFile,
GitImplementation: sourceGitArgs.gitImplementation.String(),
RecurseSubmodules: bootstrapArgs.recurseSubmodules, RecurseSubmodules: bootstrapArgs.recurseSubmodules,
} }
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
if err != nil {
return err
}
// Bootstrap config // Bootstrap config
bootstrapOpts := []bootstrap.GitProviderOption{ bootstrapOpts := []bootstrap.GitProviderOption{
bootstrap.WithProviderRepository(githubArgs.owner, githubArgs.repository, githubArgs.personal), bootstrap.WithProviderRepository(githubArgs.owner, githubArgs.repository, githubArgs.personal),
bootstrap.WithBranch(bootstrapArgs.branch), bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithBootstrapTransportType("https"), bootstrap.WithBootstrapTransportType("https"),
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail), bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix), bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithProviderTeamPermissions(mapTeamSlice(githubArgs.teams, ghDefaultPermission)), bootstrap.WithProviderTeamPermissions(mapTeamSlice(githubArgs.teams, ghDefaultPermission)),
bootstrap.WithReadWriteKeyPermissions(githubArgs.readWriteKey), bootstrap.WithReadWriteKeyPermissions(githubArgs.readWriteKey),
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions), bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
bootstrap.WithLogger(logger), bootstrap.WithLogger(logger),
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), bootstrap.WithCABundle(caBundle),
bootstrap.WithGitCommitSigning(bootstrapArgs.gpgKeyRingPath, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
} }
if bootstrapArgs.sshHostname != "" { if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))

View File

@@ -24,27 +24,27 @@ import (
"strings" "strings"
"time" "time"
"github.com/fluxcd/pkg/git" "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/fluxcd/pkg/git/gogit"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/internal/bootstrap"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/v2/pkg/bootstrap" "github.com/fluxcd/flux2/internal/bootstrap/provider"
"github.com/fluxcd/flux2/v2/pkg/bootstrap/provider" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/v2/pkg/manifestgen" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
) )
var bootstrapGitLabCmd = &cobra.Command{ var bootstrapGitLabCmd = &cobra.Command{
Use: "gitlab", Use: "gitlab",
Short: "Deploy Flux on a cluster connected to a GitLab repository", Short: "Bootstrap toolkit components in a GitLab repository",
Long: `The bootstrap gitlab command creates the GitLab repository if it doesn't exists and Long: `The bootstrap gitlab command creates the GitLab repository if it doesn't exists and
commits the Flux manifests to the specified branch. commits the toolkit components manifests to the master branch.
Then it configures the target cluster to synchronize with that repository. Then it configures the target cluster to synchronize with the repository.
If the Flux components are present on the cluster, If the toolkit components are present on the cluster,
the bootstrap command will perform an upgrade if needed.`, the bootstrap command will perform an upgrade if needed.`,
Example: ` # Create a GitLab API token and export it as an env var Example: ` # Create a GitLab API token and export it as an env var
export GITLAB_TOKEN=<my-token> export GITLAB_TOKEN=<my-token>
@@ -65,11 +65,7 @@ the bootstrap command will perform an upgrade if needed.`,
flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<domain> --token-auth flux bootstrap gitlab --owner=<group> --repository=<repository name> --hostname=<domain> --token-auth
# Run bootstrap for a an existing repository with a branch named main # Run bootstrap for a an existing repository with a branch named main
flux bootstrap gitlab --owner=<organization> --repository=<repository name> --branch=main --token-auth flux bootstrap gitlab --owner=<organization> --repository=<repository name> --branch=main --token-auth`,
# Run bootstrap for a private repository using Deploy Token authentication
flux bootstrap gitlab --owner=<group> --repository=<repository name> --deploy-token-auth
`,
RunE: bootstrapGitLabCmdRun, RunE: bootstrapGitLabCmdRun,
} }
@@ -81,17 +77,16 @@ const (
) )
type gitlabFlags struct { type gitlabFlags struct {
owner string owner string
repository string repository string
interval time.Duration interval time.Duration
personal bool personal bool
private bool private bool
hostname string hostname string
path flags.SafeRelativePath path flags.SafeRelativePath
teams []string teams []string
readWriteKey bool readWriteKey bool
reconcile bool reconcile bool
deployTokenAuth bool
} }
var gitlabArgs gitlabFlags var gitlabArgs gitlabFlags
@@ -107,7 +102,6 @@ func init() {
bootstrapGitLabCmd.Flags().Var(&gitlabArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path") bootstrapGitLabCmd.Flags().Var(&gitlabArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions") bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions")
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists") bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists")
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.deployTokenAuth, "deploy-token-auth", false, "when enabled, a Project Deploy Token is generated and will be used instead of the SSH deploy token")
bootstrapCmd.AddCommand(bootstrapGitLabCmd) bootstrapCmd.AddCommand(bootstrapGitLabCmd)
} }
@@ -129,10 +123,6 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if bootstrapArgs.tokenAuth && gitlabArgs.deployTokenAuth {
return fmt.Errorf("--token-auth and --deploy-token-auth cannot be set both.")
}
if err := bootstrapValidate(); err != nil { if err := bootstrapValidate(); err != nil {
return err return err
} }
@@ -146,9 +136,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
} }
// Manifest base // Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil { if ver, err := getVersion(bootstrapArgs.version); err == nil {
return err
} else {
bootstrapArgs.version = ver bootstrapArgs.version = ver
} }
manifestsBase, err := buildEmbeddedManifestBase() manifestsBase, err := buildEmbeddedManifestBase()
@@ -190,17 +178,10 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to create temporary working dir: %w", err) return fmt.Errorf("failed to create temporary working dir: %w", err)
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
gitClient := gogit.New(tmpDir, &http.BasicAuth{
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()} Username: gitlabArgs.owner,
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{ Password: glToken,
Transport: git.HTTPS, })
Username: gitlabArgs.owner,
Password: glToken,
CAFile: caBundle,
}, clientOpts...)
if err != nil {
return fmt.Errorf("failed to create a Git client: %w", err)
}
// Install manifest config // Install manifest config
installOptions := install.Options{ installOptions := install.Options{
@@ -234,21 +215,19 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
if bootstrapArgs.tokenAuth { if bootstrapArgs.tokenAuth {
secretOpts.Username = "git" secretOpts.Username = "git"
secretOpts.Password = glToken secretOpts.Password = glToken
secretOpts.CAFile = caBundle
} else if gitlabArgs.deployTokenAuth { if bootstrapArgs.caFile != "" {
// the actual deploy token will be reconciled later secretOpts.CAFilePath = bootstrapArgs.caFile
secretOpts.CAFile = caBundle
} else {
keypair, err := sourcesecret.LoadKeyPairFromPath(bootstrapArgs.privateKeyFile, gitArgs.password)
if err != nil {
return err
} }
secretOpts.Keypair = keypair } else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm) secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits) secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
secretOpts.SSHHostname = gitlabArgs.hostname secretOpts.SSHHostname = gitlabArgs.hostname
if bootstrapArgs.privateKeyFile != "" {
secretOpts.PrivateKeyPath = bootstrapArgs.privateKeyFile
}
if bootstrapArgs.sshHostname != "" { if bootstrapArgs.sshHostname != "" {
secretOpts.SSHHostname = bootstrapArgs.sshHostname secretOpts.SSHHostname = bootstrapArgs.sshHostname
} }
@@ -263,36 +242,30 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
Secret: bootstrapArgs.secretName, Secret: bootstrapArgs.secretName,
TargetPath: gitlabArgs.path.ToSlash(), TargetPath: gitlabArgs.path.ToSlash(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile, ManifestFile: sync.MakeDefaultOptions().ManifestFile,
GitImplementation: sourceGitArgs.gitImplementation.String(),
RecurseSubmodules: bootstrapArgs.recurseSubmodules, RecurseSubmodules: bootstrapArgs.recurseSubmodules,
} }
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
if err != nil {
return err
}
// Bootstrap config // Bootstrap config
bootstrapOpts := []bootstrap.GitProviderOption{ bootstrapOpts := []bootstrap.GitProviderOption{
bootstrap.WithProviderRepository(gitlabArgs.owner, gitlabArgs.repository, gitlabArgs.personal), bootstrap.WithProviderRepository(gitlabArgs.owner, gitlabArgs.repository, gitlabArgs.personal),
bootstrap.WithBranch(bootstrapArgs.branch), bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithBootstrapTransportType("https"), bootstrap.WithBootstrapTransportType("https"),
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail), bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix), bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithProviderTeamPermissions(mapTeamSlice(gitlabArgs.teams, glDefaultPermission)), bootstrap.WithProviderTeamPermissions(mapTeamSlice(gitlabArgs.teams, glDefaultPermission)),
bootstrap.WithReadWriteKeyPermissions(gitlabArgs.readWriteKey), bootstrap.WithReadWriteKeyPermissions(gitlabArgs.readWriteKey),
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions), bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
bootstrap.WithLogger(logger), bootstrap.WithLogger(logger),
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), bootstrap.WithCABundle(caBundle),
bootstrap.WithGitCommitSigning(bootstrapArgs.gpgKeyRingPath, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
} }
if bootstrapArgs.sshHostname != "" { if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
} }
if bootstrapArgs.tokenAuth || gitlabArgs.deployTokenAuth { if bootstrapArgs.tokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https")) bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https"))
} }
if gitlabArgs.deployTokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithDeployTokenAuth())
}
if !gitlabArgs.private { if !gitlabArgs.private {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public")) bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
} }

View File

@@ -23,7 +23,7 @@ import (
var buildCmd = &cobra.Command{ var buildCmd = &cobra.Command{
Use: "build", Use: "build",
Short: "Build a flux resource", Short: "Build a flux resource",
Long: `The build command is used to build flux resources.`, Long: "The build command is used to build flux resources.",
} }
func init() { func init() {

View File

@@ -17,10 +17,7 @@ limitations under the License.
package main package main
import ( import (
"bufio"
"bytes"
"fmt" "fmt"
"io"
"os" "os"
"strings" "strings"
@@ -33,14 +30,10 @@ import (
var buildArtifactCmd = &cobra.Command{ var buildArtifactCmd = &cobra.Command{
Use: "artifact", Use: "artifact",
Short: "Build artifact", Short: "Build artifact",
Long: withPreviewNote(`The build artifact command creates a tgz file with the manifests Long: `The build artifact command creates a tgz file with the manifests from the given directory.`,
from the given directory or a single manifest file.`),
Example: ` # Build the given manifests directory into an artifact Example: ` # Build the given manifests directory into an artifact
flux build artifact --path ./path/to/local/manifests --output ./path/to/artifact.tgz flux build artifact --path ./path/to/local/manifests --output ./path/to/artifact.tgz
# Build the given single manifest file into an artifact
flux build artifact --path ./path/to/local/manifest.yaml --output ./path/to/artifact.tgz
# List the files bundled in the artifact # List the files bundled in the artifact
tar -ztvf ./path/to/artifact.tgz tar -ztvf ./path/to/artifact.tgz
`, `,
@@ -58,7 +51,7 @@ var excludeOCI = append(strings.Split(sourceignore.ExcludeVCS, ","), strings.Spl
var buildArtifactArgs buildArtifactFlags var buildArtifactArgs buildArtifactFlags
func init() { func init() {
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.path, "path", "p", "", "Path to the directory where the Kubernetes manifests are located.") buildArtifactCmd.Flags().StringVar(&buildArtifactArgs.path, "path", "", "Path to the directory where the Kubernetes manifests are located.")
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.output, "output", "o", "artifact.tgz", "Path to where the artifact tgz file should be written.") buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.output, "output", "o", "artifact.tgz", "Path to where the artifact tgz file should be written.")
buildArtifactCmd.Flags().StringSliceVar(&buildArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format") buildArtifactCmd.Flags().StringSliceVar(&buildArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
@@ -70,48 +63,18 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid path %q", buildArtifactArgs.path) return fmt.Errorf("invalid path %q", buildArtifactArgs.path)
} }
path := buildArtifactArgs.path if fs, err := os.Stat(buildArtifactArgs.path); err != nil || !fs.IsDir() {
var err error return fmt.Errorf("invalid path '%s', must point to an existing directory", buildArtifactArgs.path)
if buildArtifactArgs.path == "-" {
path, err = saveReaderToFile(os.Stdin)
if err != nil {
return err
}
defer os.Remove(path)
} }
if _, err := os.Stat(path); err != nil { logger.Actionf("building artifact from %s", buildArtifactArgs.path)
return fmt.Errorf("invalid path '%s', must point to an existing directory or file", path)
}
logger.Actionf("building artifact from %s", path) ociClient := oci.NewLocalClient()
if err := ociClient.Build(buildArtifactArgs.output, buildArtifactArgs.path, buildArtifactArgs.ignorePaths); err != nil {
ociClient := oci.NewClient(oci.DefaultOptions())
if err := ociClient.Build(buildArtifactArgs.output, path, buildArtifactArgs.ignorePaths); err != nil {
return fmt.Errorf("bulding artifact failed, error: %w", err) return fmt.Errorf("bulding artifact failed, error: %w", err)
} }
logger.Successf("artifact created at %s", buildArtifactArgs.output) logger.Successf("artifact created at %s", buildArtifactArgs.output)
return nil return nil
} }
func saveReaderToFile(reader io.Reader) (string, error) {
b, err := io.ReadAll(bufio.NewReader(reader))
if err != nil {
return "", err
}
b = bytes.TrimRight(b, "\r\n")
f, err := os.CreateTemp("", "*.yaml")
if err != nil {
return "", fmt.Errorf("unable to create temp dir for stdin")
}
defer f.Close()
if _, err := f.Write(b); err != nil {
return "", fmt.Errorf("error writing stdin to file: %w", err)
}
return f.Name(), nil
}

View File

@@ -1,70 +0,0 @@
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"os"
"strings"
"testing"
. "github.com/onsi/gomega"
)
func Test_saveReaderToFile(t *testing.T) {
g := NewWithT(t)
testString := `apiVersion: v1
kind: ConfigMap
metadata:
name: myapp
data:
foo: bar`
tests := []struct {
name string
string string
expectErr bool
}{
{
name: "yaml",
string: testString,
},
{
name: "yaml with carriage return",
string: testString + "\r\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpFile, err := saveReaderToFile(strings.NewReader(tt.string))
g.Expect(err).To(BeNil())
t.Cleanup(func() { _ = os.Remove(tmpFile) })
b, err := os.ReadFile(tmpFile)
if tt.expectErr {
g.Expect(err).To(Not(BeNil()))
return
}
g.Expect(err).To(BeNil())
g.Expect(string(b)).To(BeEquivalentTo(testString))
})
}
}

View File

@@ -21,12 +21,10 @@ import (
"os" "os"
"os/signal" "os/signal"
"github.com/fluxcd/pkg/ssa"
"github.com/spf13/cobra" "github.com/spf13/cobra"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" "github.com/fluxcd/flux2/internal/build"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/build"
) )
var buildKsCmd = &cobra.Command{ var buildKsCmd = &cobra.Command{
@@ -42,18 +40,7 @@ It is possible to specify a Flux kustomization file using --kustomization-file.`
flux build kustomization my-app --path ./path/to/local/manifests flux build kustomization my-app --path ./path/to/local/manifests
# Build using a local flux kustomization file # Build using a local flux kustomization file
flux build kustomization my-app --path ./path/to/local/manifests --kustomization-file ./path/to/local/my-app.yaml flux build kustomization my-app --path ./path/to/local/manifests --kustomization-file ./path/to/local/my-app.yaml`,
# Build in dry-run mode without connecting to the cluster.
# Note that variable substitutions from Secrets and ConfigMaps are skipped in dry-run mode.
flux build kustomization my-app --path ./path/to/local/manifests \
--kustomization-file ./path/to/local/my-app.yaml \
--dry-run
# Exclude files by providing a comma separated list of entries that follow the .gitignore pattern fromat.
flux build kustomization my-app --path ./path/to/local/manifests \
--kustomization-file ./path/to/local/my-app.yaml \
--ignore-paths "/to_ignore/**/*.yaml,ignore.yaml"`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: buildKsCmdRun, RunE: buildKsCmdRun,
} }
@@ -61,8 +48,6 @@ flux build kustomization my-app --path ./path/to/local/manifests \
type buildKsFlags struct { type buildKsFlags struct {
kustomizationFile string kustomizationFile string
path string path string
ignorePaths []string
dryRun bool
} }
var buildKsArgs buildKsFlags var buildKsArgs buildKsFlags
@@ -70,12 +55,10 @@ var buildKsArgs buildKsFlags
func init() { func init() {
buildKsCmd.Flags().StringVar(&buildKsArgs.path, "path", "", "Path to the manifests location.") buildKsCmd.Flags().StringVar(&buildKsArgs.path, "path", "", "Path to the manifests location.")
buildKsCmd.Flags().StringVar(&buildKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.") buildKsCmd.Flags().StringVar(&buildKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.")
buildKsCmd.Flags().StringSliceVar(&buildKsArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in .gitignore format")
buildKsCmd.Flags().BoolVar(&buildKsArgs.dryRun, "dry-run", false, "Dry run mode.")
buildCmd.AddCommand(buildKsCmd) buildCmd.AddCommand(buildKsCmd)
} }
func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) { func buildKsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("%s name is required", kustomizationType.humanKind) return fmt.Errorf("%s name is required", kustomizationType.humanKind)
} }
@@ -89,34 +72,13 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
return fmt.Errorf("invalid resource path %q", buildKsArgs.path) return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
} }
if buildKsArgs.dryRun && buildKsArgs.kustomizationFile == "" {
return fmt.Errorf("dry-run mode requires a kustomization file")
}
if buildKsArgs.kustomizationFile != "" { if buildKsArgs.kustomizationFile != "" {
if fs, err := os.Stat(buildKsArgs.kustomizationFile); os.IsNotExist(err) || fs.IsDir() { if fs, err := os.Stat(buildKsArgs.kustomizationFile); os.IsNotExist(err) || fs.IsDir() {
return fmt.Errorf("invalid kustomization file %q", buildKsArgs.kustomizationFile) return fmt.Errorf("invalid kustomization file %q", buildKsArgs.kustomizationFile)
} }
} }
var builder *build.Builder builder, err := build.NewBuilder(kubeconfigArgs, kubeclientOptions, name, buildKsArgs.path, build.WithTimeout(rootArgs.timeout), build.WithKustomizationFile(buildKsArgs.kustomizationFile))
if buildKsArgs.dryRun {
builder, err = build.NewBuilder(name, buildKsArgs.path,
build.WithTimeout(rootArgs.timeout),
build.WithKustomizationFile(buildKsArgs.kustomizationFile),
build.WithDryRun(buildKsArgs.dryRun),
build.WithNamespace(*kubeconfigArgs.Namespace),
build.WithIgnore(buildKsArgs.ignorePaths),
)
} else {
builder, err = build.NewBuilder(name, buildKsArgs.path,
build.WithClientConfig(kubeconfigArgs, kubeclientOptions),
build.WithTimeout(rootArgs.timeout),
build.WithKustomizationFile(buildKsArgs.kustomizationFile),
build.WithIgnore(buildKsArgs.ignorePaths),
)
}
if err != nil { if err != nil {
return err return err
} }
@@ -127,17 +89,12 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
errChan := make(chan error) errChan := make(chan error)
go func() { go func() {
objects, err := builder.Build() manifests, err := builder.Build()
if err != nil { if err != nil {
errChan <- err errChan <- err
} }
manifests, err := ssa.ObjectsToYAML(objects) cmd.Print(string(manifests))
if err != nil {
errChan <- err
}
cmd.Print(manifests)
errChan <- nil errChan <- nil
}() }()

View File

@@ -63,12 +63,6 @@ func TestBuildKustomization(t *testing.T) {
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml", resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
assertFunc: "assertGoldenTemplateFile", assertFunc: "assertGoldenTemplateFile",
}, },
{
name: "build ignore",
args: "build kustomization podinfo --path ./testdata/build-kustomization/ignore --ignore-paths \"!configmap.yaml,!secret.yaml\"",
resultFile: "./testdata/build-kustomization/podinfo-with-ignore-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
} }
tmpl := map[string]string{ tmpl := map[string]string{
@@ -98,7 +92,7 @@ func TestBuildKustomization(t *testing.T) {
} }
func TestBuildLocalKustomization(t *testing.T) { func TestBuildLocalKustomization(t *testing.T) {
podinfo := `apiVersion: kustomize.toolkit.fluxcd.io/v1 podinfo := `apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization kind: Kustomization
metadata: metadata:
name: podinfo name: podinfo
@@ -148,12 +142,6 @@ spec:
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml", resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
assertFunc: "assertGoldenTemplateFile", assertFunc: "assertGoldenTemplateFile",
}, },
{
name: "build deployment and configmap with var substitution in dry-run mode",
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/var-substitution --dry-run",
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
} }
tmpl := map[string]string{ tmpl := map[string]string{
@@ -177,7 +165,8 @@ spec:
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(func() { _ = os.Remove("./testdata/build-kustomization/podinfo.yaml") })
defer os.Remove("./testdata/build-kustomization/podinfo.yaml")
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@@ -30,17 +30,17 @@ import (
"github.com/fluxcd/pkg/version" "github.com/fluxcd/pkg/version"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen" "github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/v2/pkg/status" "github.com/fluxcd/flux2/pkg/status"
) )
var checkCmd = &cobra.Command{ var checkCmd = &cobra.Command{
Use: "check", Use: "check",
Short: "Check requirements and installation", Short: "Check requirements and installation",
Long: withPreviewNote(`The check command will perform a series of checks to validate that Long: `The check command will perform a series of checks to validate that
the local environment is configured correctly and if the installed components are healthy.`), the local environment is configured correctly and if the installed components are healthy.`,
Example: ` # Run pre-installation checks Example: ` # Run pre-installation checks
flux check --pre flux check --pre
@@ -242,9 +242,8 @@ func crdsCheck() bool {
} }
for _, crd := range list.Items { for _, crd := range list.Items {
versions := crd.Status.StoredVersions if len(crd.Status.StoredVersions) > 0 {
if len(versions) > 0 { logger.Successf(crd.Name + "/" + crd.Status.StoredVersions[0])
logger.Successf(crd.Name + "/" + versions[len(versions)-1])
} else { } else {
ok = false ok = false
logger.Failuref("no stored versions for %s", crd.Name) logger.Failuref("no stored versions for %s", crd.Name)

View File

@@ -25,7 +25,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
func TestCheckPre(t *testing.T) { func TestCheckPre(t *testing.T) {

View File

@@ -20,7 +20,7 @@ import (
"context" "context"
"strings" "strings"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -31,7 +31,7 @@ import (
var completionCmd = &cobra.Command{ var completionCmd = &cobra.Command{
Use: "completion", Use: "completion",
Short: "Generates completion scripts for various shells", Short: "Generates completion scripts for various shells",
Long: `The completion sub-command generates completion scripts for various shells.`, Long: "The completion sub-command generates completion scripts for various shells",
} }
func init() { func init() {

View File

@@ -25,7 +25,6 @@ import (
var completionBashCmd = &cobra.Command{ var completionBashCmd = &cobra.Command{
Use: "bash", Use: "bash",
Short: "Generates bash completion scripts", Short: "Generates bash completion scripts",
Long: `The completion sub-command generates completion scripts for bash.`,
Example: `To load completion run Example: `To load completion run
. <(flux completion bash) . <(flux completion bash)

View File

@@ -25,7 +25,6 @@ import (
var completionFishCmd = &cobra.Command{ var completionFishCmd = &cobra.Command{
Use: "fish", Use: "fish",
Short: "Generates fish completion scripts", Short: "Generates fish completion scripts",
Long: `The completion sub-command generates completion scripts for fish.`,
Example: `To configure your fish shell to load completions for each session write this script to your completions dir: Example: `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.fish flux completion fish > ~/.config/fish/completions/flux.fish

View File

@@ -25,7 +25,6 @@ import (
var completionPowerShellCmd = &cobra.Command{ var completionPowerShellCmd = &cobra.Command{
Use: "powershell", Use: "powershell",
Short: "Generates powershell completion scripts", Short: "Generates powershell completion scripts",
Long: `The completion sub-command generates completion scripts for powershell.`,
Example: `To load completion run Example: `To load completion run
. <(flux completion powershell) . <(flux completion powershell)
@@ -35,12 +34,12 @@ To configure your powershell shell to load completions for each session add to y
Windows: Windows:
cd "$env:USERPROFILE\Documents\WindowsPowerShell\Modules" cd "$env:USERPROFILE\Documents\WindowsPowerShell\Modules"
flux completion powershell >> flux-completion.ps1 flux completion >> flux-completion.ps1
Linux: Linux:
cd "${XDG_CONFIG_HOME:-"$HOME/.config/"}/powershell/modules" cd "${XDG_CONFIG_HOME:-"$HOME/.config/"}/powershell/modules"
flux completion powershell >> flux-completions.ps1`, flux completion >> flux-completions.ps1`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
rootCmd.GenPowerShellCompletion(os.Stdout) rootCmd.GenPowerShellCompletion(os.Stdout)
}, },

View File

@@ -26,7 +26,6 @@ import (
var completionZshCmd = &cobra.Command{ var completionZshCmd = &cobra.Command{
Use: "zsh", Use: "zsh",
Short: "Generates zsh completion scripts", Short: "Generates zsh completion scripts",
Long: `The completion sub-command generates completion scripts for zsh.`,
Example: `To load completion run Example: `To load completion run
. <(flux completion zsh) . <(flux completion zsh)

View File

@@ -30,13 +30,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
var createCmd = &cobra.Command{ var createCmd = &cobra.Command{
Use: "create", Use: "create",
Short: "Create or update sources and resources", Short: "Create or update sources and resources",
Long: `The create sub-commands generate sources and resources.`, Long: "The create sub-commands generate sources and resources.",
} }
type createFlags struct { type createFlags struct {

View File

@@ -28,17 +28,16 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
var createAlertCmd = &cobra.Command{ var createAlertCmd = &cobra.Command{
Use: "alert [name]", Use: "alert [name]",
Short: "Create or update a Alert resource", Short: "Create or update a Alert resource",
Long: withPreviewNote(`The create alert command generates a Alert resource.`), Long: "The create alert command generates a Alert resource.",
Example: ` # Create an Alert for kustomization events Example: ` # Create an Alert for kustomization events
flux create alert \ flux create alert \
--event-severity info \ --event-severity info \
@@ -97,13 +96,13 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
logger.Generatef("generating Alert") logger.Generatef("generating Alert")
} }
alert := notificationv1b2.Alert{ alert := notificationv1.Alert{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: *kubeconfigArgs.Namespace, Namespace: *kubeconfigArgs.Namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
Spec: notificationv1b2.AlertSpec{ Spec: notificationv1.AlertSpec{
ProviderRef: meta.LocalObjectReference{ ProviderRef: meta.LocalObjectReference{
Name: alertArgs.providerRef, Name: alertArgs.providerRef,
}, },
@@ -141,13 +140,13 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
} }
func upsertAlert(ctx context.Context, kubeClient client.Client, func upsertAlert(ctx context.Context, kubeClient client.Client,
alert *notificationv1b2.Alert) (types.NamespacedName, error) { alert *notificationv1.Alert) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: alert.GetNamespace(), Namespace: alert.GetNamespace(),
Name: alert.GetName(), Name: alert.GetName(),
} }
var existing notificationv1b2.Alert var existing notificationv1.Alert
err := kubeClient.Get(ctx, namespacedName, &existing) err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil { if err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
@@ -172,7 +171,7 @@ func upsertAlert(ctx context.Context, kubeClient client.Client,
} }
func isAlertReady(ctx context.Context, kubeClient client.Client, func isAlertReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, alert *notificationv1b2.Alert) wait.ConditionFunc { namespacedName types.NamespacedName, alert *notificationv1.Alert) wait.ConditionFunc {
return func() (bool, error) { return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, alert) err := kubeClient.Get(ctx, namespacedName, alert)
if err != nil { if err != nil {

View File

@@ -28,16 +28,16 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
var createAlertProviderCmd = &cobra.Command{ var createAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]", Use: "alert-provider [name]",
Short: "Create or update a Provider resource", Short: "Create or update a Provider resource",
Long: withPreviewNote(`The create alert-provider command generates a Provider resource.`), Long: "The create alert-provider command generates a Provider resource.",
Example: ` # Create a Provider for a Slack channel Example: ` # Create a Provider for a Slack channel
flux create alert-provider slack \ flux create alert-provider slack \
--type slack \ --type slack \

View File

@@ -24,8 +24,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/transform" "github.com/fluxcd/pkg/runtime/transform"
@@ -46,7 +46,7 @@ var createHelmReleaseCmd = &cobra.Command{
Use: "helmrelease [name]", Use: "helmrelease [name]",
Aliases: []string{"hr"}, Aliases: []string{"hr"},
Short: "Create or update a HelmRelease resource", Short: "Create or update a HelmRelease resource",
Long: withPreviewNote(`The helmrelease create command generates a HelmRelease resource for a given HelmRepository source.`), Long: "The helmrelease create command generates a HelmRelease resource for a given HelmRepository source.",
Example: ` # Create a HelmRelease with a chart from a HelmRepository source Example: ` # Create a HelmRelease with a chart from a HelmRepository source
flux create hr podinfo \ flux create hr podinfo \
--interval=10m \ --interval=10m \
@@ -83,9 +83,9 @@ var createHelmReleaseCmd = &cobra.Command{
# Create a HelmRelease with a custom release name # Create a HelmRelease with a custom release name
flux create hr podinfo \ flux create hr podinfo \
--release-name=podinfo-dev \ --release-name=podinfo-dev
--source=HelmRepository/podinfo \ --source=HelmRepository/podinfo \
--chart=podinfo --chart=podinfo \
# Create a HelmRelease targeting another namespace than the resource # Create a HelmRelease targeting another namespace than the resource
flux create hr podinfo \ flux create hr podinfo \
@@ -200,7 +200,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
} }
if helmReleaseArgs.kubeConfigSecretRef != "" { if helmReleaseArgs.kubeConfigSecretRef != "" {
helmRelease.Spec.KubeConfig = &meta.KubeConfigReference{ helmRelease.Spec.KubeConfig = &helmv2.KubeConfig{
SecretRef: meta.SecretKeyReference{ SecretRef: meta.SecretKeyReference{
Name: helmReleaseArgs.kubeConfigSecretRef, Name: helmReleaseArgs.kubeConfigSecretRef,
}, },

View File

@@ -20,12 +20,14 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const createImageLong = `The create image sub-commands work with image automation objects; that is,
object controlling updates to git based on e.g., new container images
being available.`
var createImageCmd = &cobra.Command{ var createImageCmd = &cobra.Command{
Use: "image", Use: "image",
Short: "Create or update resources dealing with image automation", Short: "Create or update resources dealing with image automation",
Long: `The create image sub-commands work with image automation objects; Long: createImageLong,
that is, object controlling updates to git based on e.g., new container images
being available.`,
} }
func init() { func init() {

View File

@@ -28,18 +28,18 @@ import (
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
) )
var createImagePolicyCmd = &cobra.Command{ var createImagePolicyCmd = &cobra.Command{
Use: "policy [name]", Use: "policy [name]",
Short: "Create or update an ImagePolicy object", Short: "Create or update an ImagePolicy object",
Long: withPreviewNote(`The create image policy command generates an ImagePolicy resource. Long: `The create image policy command generates an ImagePolicy resource.
An ImagePolicy object calculates a "latest image" given an image An ImagePolicy object calculates a "latest image" given an image
repository and a policy, e.g., semver. repository and a policy, e.g., semver.
The image that sorts highest according to the policy is recorded in The image that sorts highest according to the policy is recorded in
the status of the object.`), the status of the object.`,
Example: ` # Create an ImagePolicy to select the latest stable release Example: ` # Create an ImagePolicy to select the latest stable release
flux create image policy podinfo \ flux create image policy podinfo \
--image-ref=podinfo \ --image-ref=podinfo \

View File

@@ -26,14 +26,14 @@ import (
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
) )
var createImageRepositoryCmd = &cobra.Command{ var createImageRepositoryCmd = &cobra.Command{
Use: "repository [name]", Use: "repository [name]",
Short: "Create or update an ImageRepository object", Short: "Create or update an ImageRepository object",
Long: withPreviewNote(`The create image repository command generates an ImageRepository resource. Long: `The create image repository command generates an ImageRepository resource.
An ImageRepository object specifies an image repository to scan.`), An ImageRepository object specifies an image repository to scan.`,
Example: ` # Create an ImageRepository object to scan the alpine image repository: Example: ` # Create an ImageRepository object to scan the alpine image repository:
flux create image repository alpine-repo --image alpine --interval 20m flux create image repository alpine-repo --image alpine --interval 20m

View File

@@ -23,15 +23,15 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
) )
var createImageUpdateCmd = &cobra.Command{ var createImageUpdateCmd = &cobra.Command{
Use: "update [name]", Use: "update [name]",
Short: "Create or update an ImageUpdateAutomation object", Short: "Create or update an ImageUpdateAutomation object",
Long: withPreviewNote(`The create image update command generates an ImageUpdateAutomation resource. Long: `The create image update command generates an ImageUpdateAutomation resource.
An ImageUpdateAutomation object specifies an automated update to images An ImageUpdateAutomation object specifies an automated update to images
mentioned in YAMLs in a git repository.`), mentioned in YAMLs in a git repository.`,
Example: ` # Configure image updates for the main repository created by flux bootstrap Example: ` # Configure image updates for the main repository created by flux bootstrap
flux create image update flux-system \ flux create image update flux-system \
--git-repo-ref=flux-system \ --git-repo-ref=flux-system \

View File

@@ -31,18 +31,18 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
var createKsCmd = &cobra.Command{ var createKsCmd = &cobra.Command{
Use: "kustomization [name]", Use: "kustomization [name]",
Aliases: []string{"ks"}, Aliases: []string{"ks"},
Short: "Create or update a Kustomization resource", Short: "Create or update a Kustomization resource",
Long: `The create command generates a Kustomization resource for a given source.`, Long: "The create command generates a Kustomization resource for a given source.",
Example: ` # Create a Kustomization resource from a source at a given path Example: ` # Create a Kustomization resource from a source at a given path
flux create kustomization kyverno \ flux create kustomization kyverno \
--source=GitRepository/kyverno \ --source=GitRepository/kyverno \
@@ -97,7 +97,6 @@ type kustomizationFlags struct {
targetNamespace string targetNamespace string
wait bool wait bool
kubeConfigSecretRef string kubeConfigSecretRef string
retryInterval time.Duration
} }
var kustomizationArgs = NewKustomizationFlags() var kustomizationArgs = NewKustomizationFlags()
@@ -117,7 +116,6 @@ func init() {
createKsCmd.Flags().StringVar(&kustomizationArgs.targetNamespace, "target-namespace", "", "overrides the namespace of all Kustomization objects reconciled by this Kustomization") createKsCmd.Flags().StringVar(&kustomizationArgs.targetNamespace, "target-namespace", "", "overrides the namespace of all Kustomization objects reconciled by this Kustomization")
createKsCmd.Flags().StringVar(&kustomizationArgs.kubeConfigSecretRef, "kubeconfig-secret-ref", "", "the name of the Kubernetes Secret that contains a key with the kubeconfig file for connecting to a remote cluster") createKsCmd.Flags().StringVar(&kustomizationArgs.kubeConfigSecretRef, "kubeconfig-secret-ref", "", "the name of the Kubernetes Secret that contains a key with the kubeconfig file for connecting to a remote cluster")
createKsCmd.Flags().MarkDeprecated("validation", "this arg is no longer used, all resources are validated using server-side apply dry-run") createKsCmd.Flags().MarkDeprecated("validation", "this arg is no longer used, all resources are validated using server-side apply dry-run")
createKsCmd.Flags().DurationVar(&kustomizationArgs.retryInterval, "retry-interval", 0, "the interval at which to retry a previously failed reconciliation")
createCmd.AddCommand(createKsCmd) createCmd.AddCommand(createKsCmd)
} }
@@ -171,7 +169,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
} }
if kustomizationArgs.kubeConfigSecretRef != "" { if kustomizationArgs.kubeConfigSecretRef != "" {
kustomization.Spec.KubeConfig = &meta.KubeConfigReference{ kustomization.Spec.KubeConfig = &kustomizev1.KubeConfig{
SecretRef: meta.SecretKeyReference{ SecretRef: meta.SecretKeyReference{
Name: kustomizationArgs.kubeConfigSecretRef, Name: kustomizationArgs.kubeConfigSecretRef,
}, },
@@ -240,10 +238,6 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
if kustomizationArgs.retryInterval > 0 {
kustomization.Spec.RetryInterval = &metav1.Duration{Duration: kustomizationArgs.retryInterval}
}
if createArgs.export { if createArgs.export {
return printExport(exportKs(&kustomization)) return printExport(exportKs(&kustomization))
} }

View File

@@ -28,16 +28,16 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
var createReceiverCmd = &cobra.Command{ var createReceiverCmd = &cobra.Command{
Use: "receiver [name]", Use: "receiver [name]",
Short: "Create or update a Receiver resource", Short: "Create or update a Receiver resource",
Long: `The create receiver command generates a Receiver resource.`, Long: "The create receiver command generates a Receiver resource.",
Example: ` # Create a Receiver Example: ` # Create a Receiver
flux create receiver github-receiver \ flux create receiver github-receiver \
--type github \ --type github \
@@ -145,7 +145,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
} }
logger.Successf("Receiver %s is ready", name) logger.Successf("Receiver %s is ready", name)
logger.Successf("generated webhook URL %s", receiver.Status.WebhookPath) logger.Successf("generated webhook URL %s", receiver.Status.URL)
return nil return nil
} }

View File

@@ -29,7 +29,7 @@ import (
var createSecretCmd = &cobra.Command{ var createSecretCmd = &cobra.Command{
Use: "secret", Use: "secret",
Short: "Create or update Kubernetes secrets", Short: "Create or update Kubernetes secrets",
Long: `The create source sub-commands generate Kubernetes secrets specific to Flux.`, Long: "The create source sub-commands generate Kubernetes secrets specific to Flux.",
} }
func init() { func init() {

View File

@@ -21,25 +21,22 @@ import (
"crypto/elliptic" "crypto/elliptic"
"fmt" "fmt"
"net/url" "net/url"
"os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
) )
var createSecretGitCmd = &cobra.Command{ var createSecretGitCmd = &cobra.Command{
Use: "git [name]", Use: "git [name]",
Short: "Create or update a Kubernetes secret for Git authentication", Short: "Create or update a Kubernetes secret for Git authentication",
Long: `The create secret git command generates a Kubernetes secret with Git credentials. Long: `The create secret git command generates a Kubernetes secret with Git credentials.
For Git over SSH, the host and SSH keys are automatically generated and stored For Git over SSH, the host and SSH keys are automatically generated and stored in the secret.
in the secret. For Git over HTTP/S, the provided basic authentication credentials are stored in the secret.`,
For Git over HTTP/S, the provided basic authentication credentials or bearer
authentication token are stored in the secret.`,
Example: ` # Create a Git SSH authentication secret using an ECDSA P-521 curve public key Example: ` # Create a Git SSH authentication secret using an ECDSA P-521 curve public key
flux create secret git podinfo-auth \ flux create secret git podinfo-auth \
@@ -89,7 +86,6 @@ type secretGitFlags struct {
ecdsaCurve flags.ECDSACurve ecdsaCurve flags.ECDSACurve
caFile string caFile string
privateKeyFile string privateKeyFile string
bearerToken string
} }
var secretGitArgs = NewSecretGitFlags() var secretGitArgs = NewSecretGitFlags()
@@ -103,7 +99,6 @@ func init() {
createSecretGitCmd.Flags().Var(&secretGitArgs.ecdsaCurve, "ssh-ecdsa-curve", secretGitArgs.ecdsaCurve.Description()) createSecretGitCmd.Flags().Var(&secretGitArgs.ecdsaCurve, "ssh-ecdsa-curve", secretGitArgs.ecdsaCurve.Description())
createSecretGitCmd.Flags().StringVar(&secretGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates") createSecretGitCmd.Flags().StringVar(&secretGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
createSecretGitCmd.Flags().StringVar(&secretGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server") createSecretGitCmd.Flags().StringVar(&secretGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
createSecretGitCmd.Flags().StringVar(&secretGitArgs.bearerToken, "bearer-token", "", "bearer authentication token")
createSecretCmd.AddCommand(createSecretGitCmd) createSecretCmd.AddCommand(createSecretGitCmd)
} }
@@ -140,33 +135,19 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
} }
switch u.Scheme { switch u.Scheme {
case "ssh": case "ssh":
keypair, err := sourcesecret.LoadKeyPairFromPath(secretGitArgs.privateKeyFile, secretGitArgs.password)
if err != nil {
return err
}
opts.Keypair = keypair
opts.SSHHostname = u.Host opts.SSHHostname = u.Host
opts.PrivateKeyPath = secretGitArgs.privateKeyFile
opts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(secretGitArgs.keyAlgorithm) opts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(secretGitArgs.keyAlgorithm)
opts.RSAKeyBits = int(secretGitArgs.rsaBits) opts.RSAKeyBits = int(secretGitArgs.rsaBits)
opts.ECDSACurve = secretGitArgs.ecdsaCurve.Curve opts.ECDSACurve = secretGitArgs.ecdsaCurve.Curve
opts.Password = secretGitArgs.password opts.Password = secretGitArgs.password
case "http", "https": case "http", "https":
if (secretGitArgs.username == "" || secretGitArgs.password == "") && secretGitArgs.bearerToken == "" { if secretGitArgs.username == "" || secretGitArgs.password == "" {
return fmt.Errorf("for Git over HTTP/S the username and password, or a bearer token is required") return fmt.Errorf("for Git over HTTP/S the username and password are required")
} }
opts.Username = secretGitArgs.username opts.Username = secretGitArgs.username
opts.Password = secretGitArgs.password opts.Password = secretGitArgs.password
opts.BearerToken = secretGitArgs.bearerToken opts.CAFilePath = secretGitArgs.caFile
if secretGitArgs.username != "" && secretGitArgs.password != "" && secretGitArgs.bearerToken != "" {
return fmt.Errorf("user credentials and bearer token cannot be used together")
}
if secretGitArgs.caFile != "" {
caBundle, err := os.ReadFile(secretGitArgs.caFile)
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
opts.CAFile = caBundle
}
default: default:
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme) return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
} }

View File

@@ -30,16 +30,6 @@ func TestCreateGitSecret(t *testing.T) {
args: "create secret git podinfo-auth --url=ssh://git@github.com/stefanprodan/podinfo --private-key-file=./testdata/create_secret/git/ecdsa-password.private --password=password --namespace=my-namespace --export", args: "create secret git podinfo-auth --url=ssh://git@github.com/stefanprodan/podinfo --private-key-file=./testdata/create_secret/git/ecdsa-password.private --password=password --namespace=my-namespace --export",
assert: assertGoldenFile("testdata/create_secret/git/git-ssh-secret-password.yaml"), assert: assertGoldenFile("testdata/create_secret/git/git-ssh-secret-password.yaml"),
}, },
{
name: "git authentication with bearer token",
args: "create secret git bearer-token-auth --url=https://github.com/stefanprodan/podinfo --bearer-token=ghp_baR2qnFF0O41WlucePL3udt2N9vVZS4R0hAS --namespace=my-namespace --export",
assert: assertGoldenFile("testdata/create_secret/git/git-bearer-token.yaml"),
},
{
name: "git authentication with basic auth and bearer token",
args: "create secret git podinfo-auth --url=https://github.com/stefanprodan/podinfo --username=aaa --password=zzzz --bearer-token=aaaa --namespace=my-namespace --export",
assert: assertError("user credentials and bearer token cannot be used together"),
},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@@ -18,21 +18,19 @@ package main
import ( import (
"context" "context"
"fmt"
"os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
) )
var createSecretHelmCmd = &cobra.Command{ var createSecretHelmCmd = &cobra.Command{
Use: "helm [name]", Use: "helm [name]",
Short: "Create or update a Kubernetes secret for Helm repository authentication", Short: "Create or update a Kubernetes secret for Helm repository authentication",
Long: withPreviewNote(`The create secret helm command generates a Kubernetes secret with basic authentication credentials.`), 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 \ flux create secret helm repo-auth \
--namespace=my-namespace \ --namespace=my-namespace \
@@ -76,34 +74,15 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
caBundle := []byte{}
if secretHelmArgs.caFile != "" {
var err error
caBundle, err = os.ReadFile(secretHelmArgs.caFile)
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
}
var certFile, keyFile []byte
if secretHelmArgs.certFile != "" && secretHelmArgs.keyFile != "" {
if certFile, err = os.ReadFile(secretHelmArgs.certFile); err != nil {
return fmt.Errorf("failed to read cert file: %w", err)
}
if keyFile, err = os.ReadFile(secretHelmArgs.keyFile); err != nil {
return fmt.Errorf("failed to read key file: %w", err)
}
}
opts := sourcesecret.Options{ opts := sourcesecret.Options{
Name: name, Name: name,
Namespace: *kubeconfigArgs.Namespace, Namespace: *kubeconfigArgs.Namespace,
Labels: labels, Labels: labels,
Username: secretHelmArgs.username, Username: secretHelmArgs.username,
Password: secretHelmArgs.password, Password: secretHelmArgs.password,
CAFile: caBundle, CAFilePath: secretHelmArgs.caFile,
CertFile: certFile, CertFilePath: secretHelmArgs.certFile,
KeyFile: keyFile, KeyFilePath: secretHelmArgs.keyFile,
} }
secret, err := sourcesecret.Generate(opts) secret, err := sourcesecret.Generate(opts)
if err != nil { if err != nil {

View File

@@ -20,8 +20,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/name"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -31,7 +31,7 @@ import (
var createSecretOCICmd = &cobra.Command{ var createSecretOCICmd = &cobra.Command{
Use: "oci [name]", Use: "oci [name]",
Short: "Create or update a Kubernetes image pull secret", Short: "Create or update a Kubernetes image pull secret",
Long: withPreviewNote(`The create secret oci command generates a Kubernetes secret that can be used for OCIRepository authentication`), Long: `The create secret oci command generates a Kubernetes secret that can be used for OCIRepository authentication`,
Example: ` # Create an OCI authentication secret on disk and encrypt it with Mozilla SOPS Example: ` # Create an OCI authentication secret on disk and encrypt it with Mozilla SOPS
flux create secret oci podinfo-auth \ flux create secret oci podinfo-auth \
--url=ghcr.io \ --url=ghcr.io \

View File

@@ -18,22 +18,20 @@ package main
import ( import (
"context" "context"
"fmt"
"os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
) )
var createSecretTLSCmd = &cobra.Command{ var createSecretTLSCmd = &cobra.Command{
Use: "tls [name]", Use: "tls [name]",
Short: "Create or update a Kubernetes secret with TLS certificates", Short: "Create or update a Kubernetes secret with TLS certificates",
Long: withPreviewNote(`The create secret tls command generates a Kubernetes secret with certificates for use with TLS.`), Long: `The create secret tls command generates a Kubernetes secret with certificates for use with TLS.`,
Example: ` # Create a TLS secret on disk and encrypt it with Mozilla SOPS. Example: ` # Create a TLS secret on disk and encrypt it with Mozilla SOPS.
# Files are expected to be PEM-encoded. # Files are expected to be PEM-encoded.
flux create secret tls certs \ flux create secret tls certs \
@@ -75,32 +73,13 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
caBundle := []byte{}
if secretTLSArgs.caFile != "" {
var err error
caBundle, err = os.ReadFile(secretTLSArgs.caFile)
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
}
var certFile, keyFile []byte
if secretTLSArgs.certFile != "" && secretTLSArgs.keyFile != "" {
if certFile, err = os.ReadFile(secretTLSArgs.certFile); err != nil {
return fmt.Errorf("failed to read cert file: %w", err)
}
if keyFile, err = os.ReadFile(secretTLSArgs.keyFile); err != nil {
return fmt.Errorf("failed to read key file: %w", err)
}
}
opts := sourcesecret.Options{ opts := sourcesecret.Options{
Name: name, Name: name,
Namespace: *kubeconfigArgs.Namespace, Namespace: *kubeconfigArgs.Namespace,
Labels: labels, Labels: labels,
CAFile: caBundle, CAFilePath: secretTLSArgs.caFile,
CertFile: certFile, CertFilePath: secretTLSArgs.certFile,
KeyFile: keyFile, KeyFilePath: secretTLSArgs.keyFile,
} }
secret, err := sourcesecret.Generate(opts) secret, err := sourcesecret.Generate(opts)
if err != nil { if err != nil {

View File

@@ -25,7 +25,7 @@ import (
var createSourceCmd = &cobra.Command{ var createSourceCmd = &cobra.Command{
Use: "source", Use: "source",
Short: "Create or update sources", Short: "Create or update sources",
Long: `The create source sub-commands generate sources.`, Long: "The create source sub-commands generate sources.",
} }
type createSourceFlags struct { type createSourceFlags struct {

View File

@@ -35,15 +35,15 @@ import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
var createSourceBucketCmd = &cobra.Command{ var createSourceBucketCmd = &cobra.Command{
Use: "bucket [name]", Use: "bucket [name]",
Short: "Create or update a Bucket source", Short: "Create or update a Bucket source",
Long: withPreviewNote(`The create source bucket command generates a Bucket resource and waits for it to be downloaded. Long: `The create source bucket command generates a Bucket resource and waits for it to be downloaded.
For Buckets with static authentication, the credentials are stored in a Kubernetes secret.`), For Buckets with static authentication, the credentials are stored in a Kubernetes secret.`,
Example: ` # Create a source for a Bucket using static authentication Example: ` # Create a source for a Bucket using static authentication
flux create source bucket podinfo \ flux create source bucket podinfo \
--bucket-name=podinfo \ --bucket-name=podinfo \

View File

@@ -37,11 +37,11 @@ import (
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/runtime/conditions"
sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
) )
type sourceGitFlags struct { type sourceGitFlags struct {
@@ -49,14 +49,13 @@ type sourceGitFlags struct {
branch string branch string
tag string tag string
semver string semver string
refName string
commit string
username string username string
password string password string
keyAlgorithm flags.PublicKeyAlgorithm keyAlgorithm flags.PublicKeyAlgorithm
keyRSABits flags.RSAKeyBits keyRSABits flags.RSAKeyBits
keyECDSACurve flags.ECDSACurve keyECDSACurve flags.ECDSACurve
secretRef string secretRef string
gitImplementation flags.GitImplementation
caFile string caFile string
privateKeyFile string privateKeyFile string
recurseSubmodules bool recurseSubmodules bool
@@ -131,14 +130,13 @@ func init() {
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.branch, "branch", "", "git branch") createSourceGitCmd.Flags().StringVar(&sourceGitArgs.branch, "branch", "", "git branch")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.tag, "tag", "", "git tag") createSourceGitCmd.Flags().StringVar(&sourceGitArgs.tag, "tag", "", "git tag")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.semver, "tag-semver", "", "git tag semver range") createSourceGitCmd.Flags().StringVar(&sourceGitArgs.semver, "tag-semver", "", "git tag semver range")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.refName, "ref-name", "", " git reference name")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.commit, "commit", "", "git commit")
createSourceGitCmd.Flags().StringVarP(&sourceGitArgs.username, "username", "u", "", "basic authentication username") createSourceGitCmd.Flags().StringVarP(&sourceGitArgs.username, "username", "u", "", "basic authentication username")
createSourceGitCmd.Flags().StringVarP(&sourceGitArgs.password, "password", "p", "", "basic authentication password") 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.keyAlgorithm, "ssh-key-algorithm", sourceGitArgs.keyAlgorithm.Description())
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyRSABits, "ssh-rsa-bits", sourceGitArgs.keyRSABits.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().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().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") createSourceGitCmd.Flags().StringVar(&sourceGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server") createSourceGitCmd.Flags().StringVar(&sourceGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
createSourceGitCmd.Flags().BoolVar(&sourceGitArgs.recurseSubmodules, "recurse-submodules", false, createSourceGitCmd.Flags().BoolVar(&sourceGitArgs.recurseSubmodules, "recurse-submodules", false,
@@ -172,14 +170,18 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme) return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
} }
if sourceGitArgs.branch == "" && sourceGitArgs.tag == "" && sourceGitArgs.semver == "" && sourceGitArgs.commit == "" && sourceGitArgs.refName == "" { if sourceGitArgs.branch == "" && sourceGitArgs.tag == "" && sourceGitArgs.semver == "" {
return fmt.Errorf("a Git ref is required, use one of the following: --branch, --tag, --commit, --ref-name or --tag-semver") return fmt.Errorf("a Git ref is required, use one of the following: --branch, --tag or --tag-semver")
} }
if sourceGitArgs.caFile != "" && u.Scheme == "ssh" { if sourceGitArgs.caFile != "" && u.Scheme == "ssh" {
return fmt.Errorf("specifying a CA file is not supported for Git over SSH") return fmt.Errorf("specifying a CA file is not supported for Git over SSH")
} }
if sourceGitArgs.recurseSubmodules && sourceGitArgs.gitImplementation == sourcev1.LibGit2Implementation {
return fmt.Errorf("recurse submodules requires --git-implementation=%s", sourcev1.GoGitImplementation)
}
tmpDir, err := os.MkdirTemp("", name) tmpDir, err := os.MkdirTemp("", name)
if err != nil { if err != nil {
return err return err
@@ -218,12 +220,11 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
gitRepository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout} gitRepository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}
} }
if sourceGitArgs.commit != "" { if sourceGitArgs.gitImplementation != "" {
gitRepository.Spec.Reference.Commit = sourceGitArgs.commit gitRepository.Spec.GitImplementation = sourceGitArgs.gitImplementation.String()
gitRepository.Spec.Reference.Branch = sourceGitArgs.branch }
} else if sourceGitArgs.refName != "" {
gitRepository.Spec.Reference.Name = sourceGitArgs.refName if sourceGitArgs.semver != "" {
} else if sourceGitArgs.semver != "" {
gitRepository.Spec.Reference.SemVer = sourceGitArgs.semver gitRepository.Spec.Reference.SemVer = sourceGitArgs.semver
} else if sourceGitArgs.tag != "" { } else if sourceGitArgs.tag != "" {
gitRepository.Spec.Reference.Tag = sourceGitArgs.tag gitRepository.Spec.Reference.Tag = sourceGitArgs.tag
@@ -258,26 +259,16 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
switch u.Scheme { switch u.Scheme {
case "ssh": case "ssh":
keypair, err := sourcesecret.LoadKeyPairFromPath(sourceGitArgs.privateKeyFile, sourceGitArgs.password)
if err != nil {
return err
}
secretOpts.Keypair = keypair
secretOpts.SSHHostname = u.Host secretOpts.SSHHostname = u.Host
secretOpts.PrivateKeyPath = sourceGitArgs.privateKeyFile
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(sourceGitArgs.keyAlgorithm) secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(sourceGitArgs.keyAlgorithm)
secretOpts.RSAKeyBits = int(sourceGitArgs.keyRSABits) secretOpts.RSAKeyBits = int(sourceGitArgs.keyRSABits)
secretOpts.ECDSACurve = sourceGitArgs.keyECDSACurve.Curve secretOpts.ECDSACurve = sourceGitArgs.keyECDSACurve.Curve
secretOpts.Password = sourceGitArgs.password secretOpts.Password = sourceGitArgs.password
case "https": case "https":
if sourceGitArgs.caFile != "" {
caBundle, err := os.ReadFile(sourceGitArgs.caFile)
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
secretOpts.CAFile = caBundle
}
secretOpts.Username = sourceGitArgs.username secretOpts.Username = sourceGitArgs.username
secretOpts.Password = sourceGitArgs.password secretOpts.Password = sourceGitArgs.password
secretOpts.CAFilePath = sourceGitArgs.caFile
case "http": case "http":
logger.Warningf("insecure configuration: credentials configured for an HTTP URL") logger.Warningf("insecure configuration: credentials configured for an HTTP URL")
secretOpts.Username = sourceGitArgs.username secretOpts.Username = sourceGitArgs.username

View File

@@ -24,15 +24,14 @@ import (
"testing" "testing"
"time" "time"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta" apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
) )
var pollInterval = 50 * time.Millisecond var pollInterval = 50 * time.Millisecond
@@ -99,41 +98,6 @@ func TestCreateSourceGitExport(t *testing.T) {
command, command,
assertGoldenFile("testdata/create_source_git/export.golden"), assertGoldenFile("testdata/create_source_git/export.golden"),
}, },
{
name: "no args",
args: "create secret git",
assert: assertError("name is required"),
},
{
name: "source with commit",
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --commit=c88a2f41 --interval=1m0s --export",
assert: assertGoldenFile("./testdata/create_source_git/source-git-commit.yaml"),
},
{
name: "source with ref name",
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --ref-name=refs/heads/main --interval=1m0s --export",
assert: assertGoldenFile("testdata/create_source_git/source-git-refname.yaml"),
},
{
name: "source with branch name and commit",
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --branch=main --commit=c88a2f41 --interval=1m0s --export",
assert: assertGoldenFile("testdata/create_source_git/source-git-branch-commit.yaml"),
},
{
name: "source with semver",
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --tag-semver=v1.01 --interval=1m0s --export",
assert: assertGoldenFile("testdata/create_source_git/source-git-semver.yaml"),
},
{
name: "source with git tag",
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --tag=test --interval=1m0s --export",
assert: assertGoldenFile("testdata/create_source_git/source-git-tag.yaml"),
},
{
name: "source with git branch",
args: "create source git podinfo --namespace=flux-system --url=https://github.com/stefanprodan/podinfo --branch=test --interval=1m0s --export",
assert: assertGoldenFile("testdata/create_source_git/source-git-branch.yaml"),
},
} }
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
@@ -177,9 +141,6 @@ func TestCreateSourceGit(t *testing.T) {
repo.Status.Artifact = &sourcev1.Artifact{ repo.Status.Artifact = &sourcev1.Artifact{
Path: "some-path", Path: "some-path",
Revision: "v1", Revision: "v1",
LastUpdateTime: metav1.Time{
Time: time.Now(),
},
} }
}, },
}, { }, {

View File

@@ -35,15 +35,15 @@ import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
) )
var createSourceHelmCmd = &cobra.Command{ var createSourceHelmCmd = &cobra.Command{
Use: "helm [name]", Use: "helm [name]",
Short: "Create or update a HelmRepository source", Short: "Create or update a HelmRepository source",
Long: withPreviewNote(`The create source helm command generates a HelmRepository resource and waits for it to fetch the index. Long: `The create source helm command generates a HelmRepository resource and waits for it to fetch the index.
For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`), For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`,
Example: ` # Create a source for an HTTPS public Helm repository Example: ` # Create a source for an HTTPS public Helm repository
flux create source helm podinfo \ flux create source helm podinfo \
--url=https://stefanprodan.github.io/podinfo \ --url=https://stefanprodan.github.io/podinfo \
@@ -64,13 +64,13 @@ For private Helm repositories, the basic authentication credentials are stored i
# Create a source for an OCI Helm repository # Create a source for an OCI Helm repository
flux create source helm podinfo \ flux create source helm podinfo \
--url=oci://ghcr.io/stefanprodan/charts/podinfo \ --url=oci://ghcr.io/stefanprodan/charts/podinfo
--username=username \ --username=username \
--password=password --password=password
# Create a source for an OCI Helm repository using an existing secret with basic auth or dockerconfig credentials # Create a source for an OCI Helm repository using an existing secret with basic auth or dockerconfig credentials
flux create source helm podinfo \ flux create source helm podinfo \
--url=oci://ghcr.io/stefanprodan/charts/podinfo \ --url=oci://ghcr.io/stefanprodan/charts/podinfo
--secret-ref=docker-config`, --secret-ref=docker-config`,
RunE: createSourceHelmCmdRun, RunE: createSourceHelmCmdRun,
} }
@@ -83,7 +83,6 @@ type sourceHelmFlags struct {
keyFile string keyFile string
caFile string caFile string
secretRef string secretRef string
ociProvider string
passCredentials bool passCredentials bool
} }
@@ -97,7 +96,6 @@ func init() {
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.keyFile, "key-file", "", "TLS authentication key file path") createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.keyFile, "key-file", "", "TLS authentication key file path")
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.caFile, "ca-file", "", "TLS authentication CA file path") createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.caFile, "ca-file", "", "TLS authentication CA file path")
createSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.secretRef, "secret-ref", "", "", "the name of an existing secret containing TLS, basic auth or docker-config credentials") createSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.secretRef, "secret-ref", "", "", "the name of an existing secret containing TLS, basic auth or docker-config credentials")
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.ociProvider, "oci-provider", "", "OCI provider for authentication")
createSourceHelmCmd.Flags().BoolVarP(&sourceHelmArgs.passCredentials, "pass-credentials", "", false, "pass credentials to all domains") createSourceHelmCmd.Flags().BoolVarP(&sourceHelmArgs.passCredentials, "pass-credentials", "", false, "pass credentials to all domains")
createSourceCmd.AddCommand(createSourceHelmCmd) createSourceCmd.AddCommand(createSourceHelmCmd)
@@ -145,7 +143,6 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
} }
if url.Scheme == sourcev1.HelmRepositoryTypeOCI { if url.Scheme == sourcev1.HelmRepositoryTypeOCI {
helmRepository.Spec.Type = sourcev1.HelmRepositoryTypeOCI helmRepository.Spec.Type = sourcev1.HelmRepositoryTypeOCI
helmRepository.Spec.Provider = sourceHelmArgs.ociProvider
} }
if createSourceArgs.fetchTimeout > 0 { if createSourceArgs.fetchTimeout > 0 {
@@ -171,25 +168,6 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
caBundle := []byte{}
if sourceHelmArgs.caFile != "" {
var err error
caBundle, err = os.ReadFile(sourceHelmArgs.caFile)
if err != nil {
return fmt.Errorf("unable to read TLS CA file: %w", err)
}
}
var certFile, keyFile []byte
if sourceHelmArgs.certFile != "" && sourceHelmArgs.keyFile != "" {
if certFile, err = os.ReadFile(sourceHelmArgs.certFile); err != nil {
return fmt.Errorf("failed to read cert file: %w", err)
}
if keyFile, err = os.ReadFile(sourceHelmArgs.keyFile); err != nil {
return fmt.Errorf("failed to read key file: %w", err)
}
}
logger.Generatef("generating HelmRepository source") logger.Generatef("generating HelmRepository source")
if sourceHelmArgs.secretRef == "" { if sourceHelmArgs.secretRef == "" {
secretName := fmt.Sprintf("helm-%s", name) secretName := fmt.Sprintf("helm-%s", name)
@@ -198,9 +176,9 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
Namespace: *kubeconfigArgs.Namespace, Namespace: *kubeconfigArgs.Namespace,
Username: sourceHelmArgs.username, Username: sourceHelmArgs.username,
Password: sourceHelmArgs.password, Password: sourceHelmArgs.password,
CAFile: caBundle, CertFilePath: sourceHelmArgs.certFile,
CertFile: certFile, KeyFilePath: sourceHelmArgs.keyFile,
KeyFile: keyFile, CAFilePath: sourceHelmArgs.caFile,
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile, ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
} }
secret, err := sourcesecret.Generate(secretOpts) secret, err := sourcesecret.Generate(secretOpts)

View File

@@ -33,14 +33,14 @@ import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
var createSourceOCIRepositoryCmd = &cobra.Command{ var createSourceOCIRepositoryCmd = &cobra.Command{
Use: "oci [name]", Use: "oci [name]",
Short: "Create or update an OCIRepository", Short: "Create or update an OCIRepository",
Long: withPreviewNote(`The create source oci command generates an OCIRepository resource and waits for it to be ready.`), Long: `The create source oci command generates an OCIRepository resource and waits for it to be ready.`,
Example: ` # Create an OCIRepository for a public container image Example: ` # Create an OCIRepository for a public container image
flux create source oci podinfo \ flux create source oci podinfo \
--url=oci://ghcr.io/stefanprodan/manifests/podinfo \ --url=oci://ghcr.io/stefanprodan/manifests/podinfo \

View File

@@ -38,12 +38,12 @@ func TestCreateSourceOCI(t *testing.T) {
}, },
{ {
name: "export manifest", name: "export manifest",
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --export", args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --export",
assertFunc: assertGoldenFile("./testdata/oci/export.golden"), assertFunc: assertGoldenFile("./testdata/oci/export.golden"),
}, },
{ {
name: "export manifest with secret", name: "export manifest with secret",
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --secret-ref=creds --export", args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --secret-ref=creds --export",
assertFunc: assertGoldenFile("./testdata/oci/export_with_secret.golden"), assertFunc: assertGoldenFile("./testdata/oci/export_with_secret.golden"),
}, },
} }

View File

@@ -21,7 +21,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
@@ -37,8 +37,8 @@ import (
var createTenantCmd = &cobra.Command{ var createTenantCmd = &cobra.Command{
Use: "tenant", Use: "tenant",
Short: "Create or update a tenant", Short: "Create or update a tenant",
Long: withPreviewNote(`The create tenant command generates namespaces, service accounts and role bindings to limit the Long: `The create tenant command generates namespaces, service accounts and role bindings to limit the
reconcilers scope to the tenant namespaces.`), reconcilers scope to the tenant namespaces.`,
Example: ` # Create a tenant with access to a namespace Example: ` # Create a tenant with access to a namespace
flux create tenant dev-team \ flux create tenant dev-team \
--with-namespace=frontend \ --with-namespace=frontend \

View File

@@ -24,13 +24,13 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
var deleteCmd = &cobra.Command{ var deleteCmd = &cobra.Command{
Use: "delete", Use: "delete",
Short: "Delete sources and resources", Short: "Delete sources and resources",
Long: `The delete sub-commands delete sources and resources.`, Long: "The delete sub-commands delete sources and resources.",
} }
type deleteFlags struct { type deleteFlags struct {

View File

@@ -19,13 +19,13 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var deleteAlertCmd = &cobra.Command{ var deleteAlertCmd = &cobra.Command{
Use: "alert [name]", Use: "alert [name]",
Short: "Delete a Alert resource", Short: "Delete a Alert resource",
Long: withPreviewNote("The delete alert command removes the given Alert from the cluster."), Long: "The delete alert command removes the given Alert from the cluster.",
Example: ` # Delete an Alert and the Kubernetes resources created by it Example: ` # Delete an Alert and the Kubernetes resources created by it
flux delete alert main`, flux delete alert main`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)), ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),

View File

@@ -19,13 +19,13 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var deleteAlertProviderCmd = &cobra.Command{ var deleteAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]", Use: "alert-provider [name]",
Short: "Delete a Provider resource", Short: "Delete a Provider resource",
Long: withPreviewNote("The delete alert-provider command removes the given Provider from the cluster."), Long: "The delete alert-provider command removes the given Provider from the cluster.",
Example: ` # Delete a Provider and the Kubernetes resources created by it Example: ` # Delete a Provider and the Kubernetes resources created by it
flux delete alert-provider slack`, flux delete alert-provider slack`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)), ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),

View File

@@ -26,7 +26,7 @@ var deleteHelmReleaseCmd = &cobra.Command{
Use: "helmrelease [name]", Use: "helmrelease [name]",
Aliases: []string{"hr"}, Aliases: []string{"hr"},
Short: "Delete a HelmRelease resource", Short: "Delete a HelmRelease resource",
Long: withPreviewNote("The delete helmrelease command removes the given HelmRelease from the cluster."), Long: "The delete helmrelease command removes the given HelmRelease from the cluster.",
Example: ` # Delete a Helm release and the Kubernetes resources created by it Example: ` # Delete a Helm release and the Kubernetes resources created by it
flux delete hr podinfo`, flux delete hr podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)), ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),

View File

@@ -23,7 +23,7 @@ import (
var deleteImageCmd = &cobra.Command{ var deleteImageCmd = &cobra.Command{
Use: "image", Use: "image",
Short: "Delete image automation objects", Short: "Delete image automation objects",
Long: `The delete image sub-commands delete image automation objects.`, Long: "The delete image sub-commands delete image automation objects.",
} }
func init() { func init() {

View File

@@ -19,13 +19,13 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
) )
var deleteImagePolicyCmd = &cobra.Command{ var deleteImagePolicyCmd = &cobra.Command{
Use: "policy [name]", Use: "policy [name]",
Short: "Delete an ImagePolicy object", Short: "Delete an ImagePolicy object",
Long: withPreviewNote(`The delete image policy command deletes the given ImagePolicy from the cluster.`), Long: "The delete image policy command deletes the given ImagePolicy from the cluster.",
Example: ` # Delete an image policy Example: ` # Delete an image policy
flux delete image policy alpine3.x`, flux delete image policy alpine3.x`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)), ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),

View File

@@ -19,13 +19,13 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
) )
var deleteImageRepositoryCmd = &cobra.Command{ var deleteImageRepositoryCmd = &cobra.Command{
Use: "repository [name]", Use: "repository [name]",
Short: "Delete an ImageRepository object", Short: "Delete an ImageRepository object",
Long: withPreviewNote("The delete image repository command deletes the given ImageRepository from the cluster."), Long: "The delete image repository command deletes the given ImageRepository from the cluster.",
Example: ` # Delete an image repository Example: ` # Delete an image repository
flux delete image repository alpine`, flux delete image repository alpine`,
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)), ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),

View File

@@ -25,7 +25,7 @@ import (
var deleteImageUpdateCmd = &cobra.Command{ var deleteImageUpdateCmd = &cobra.Command{
Use: "update [name]", Use: "update [name]",
Short: "Delete an ImageUpdateAutomation object", Short: "Delete an ImageUpdateAutomation object",
Long: withPreviewNote(`The delete image update command deletes the given ImageUpdateAutomation from the cluster.`), Long: "The delete image update command deletes the given ImageUpdateAutomation from the cluster.",
Example: ` # Delete an image update automation Example: ` # Delete an image update automation
flux delete image update latest-images`, flux delete image update latest-images`,
ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)), ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),

View File

@@ -19,14 +19,14 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
) )
var deleteKsCmd = &cobra.Command{ var deleteKsCmd = &cobra.Command{
Use: "kustomization [name]", Use: "kustomization [name]",
Aliases: []string{"ks"}, Aliases: []string{"ks"},
Short: "Delete a Kustomization resource", Short: "Delete a Kustomization resource",
Long: `The delete kustomization command deletes the given Kustomization from the cluster.`, Long: "The delete kustomization command deletes the given Kustomization from the cluster.",
Example: ` # Delete a kustomization and the Kubernetes resources created by it when prune is enabled Example: ` # Delete a kustomization and the Kubernetes resources created by it when prune is enabled
flux delete kustomization podinfo`, flux delete kustomization podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),

View File

@@ -19,13 +19,13 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var deleteReceiverCmd = &cobra.Command{ var deleteReceiverCmd = &cobra.Command{
Use: "receiver [name]", Use: "receiver [name]",
Short: "Delete a Receiver resource", Short: "Delete a Receiver resource",
Long: `The delete receiver command removes the given Receiver from the cluster.`, Long: "The delete receiver command removes the given Receiver from the cluster.",
Example: ` # Delete an Receiver and the Kubernetes resources created by it Example: ` # Delete an Receiver and the Kubernetes resources created by it
flux delete receiver main`, flux delete receiver main`,
ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)), ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),

View File

@@ -23,7 +23,7 @@ import (
var deleteSourceCmd = &cobra.Command{ var deleteSourceCmd = &cobra.Command{
Use: "source", Use: "source",
Short: "Delete sources", Short: "Delete sources",
Long: `The delete source sub-commands delete sources.`, Long: "The delete source sub-commands delete sources.",
} }
func init() { func init() {

View File

@@ -25,7 +25,7 @@ import (
var deleteSourceBucketCmd = &cobra.Command{ var deleteSourceBucketCmd = &cobra.Command{
Use: "bucket [name]", Use: "bucket [name]",
Short: "Delete a Bucket source", Short: "Delete a Bucket source",
Long: withPreviewNote("The delete source bucket command deletes the given Bucket from the cluster."), Long: "The delete source bucket command deletes the given Bucket from the cluster.",
Example: ` # Delete a Bucket source Example: ` # Delete a Bucket source
flux delete source bucket podinfo`, flux delete source bucket podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)), ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),

View File

@@ -19,13 +19,13 @@ package main
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
) )
var deleteSourceGitCmd = &cobra.Command{ var deleteSourceGitCmd = &cobra.Command{
Use: "git [name]", Use: "git [name]",
Short: "Delete a GitRepository source", Short: "Delete a GitRepository source",
Long: `The delete source git command deletes the given GitRepository from the cluster.`, Long: "The delete source git command deletes the given GitRepository from the cluster.",
Example: ` # Delete a Git repository Example: ` # Delete a Git repository
flux delete source git podinfo`, flux delete source git podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)), ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),

View File

@@ -25,7 +25,7 @@ import (
var deleteSourceHelmCmd = &cobra.Command{ var deleteSourceHelmCmd = &cobra.Command{
Use: "helm [name]", Use: "helm [name]",
Short: "Delete a HelmRepository source", Short: "Delete a HelmRepository source",
Long: withPreviewNote("The delete source helm command deletes the given HelmRepository from the cluster."), Long: "The delete source helm command deletes the given HelmRepository from the cluster.",
Example: ` # Delete a Helm repository Example: ` # Delete a Helm repository
flux delete source helm podinfo`, flux delete source helm podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)), ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),

View File

@@ -25,7 +25,7 @@ import (
var deleteSourceOCIRepositoryCmd = &cobra.Command{ var deleteSourceOCIRepositoryCmd = &cobra.Command{
Use: "oci [name]", Use: "oci [name]",
Short: "Delete an OCIRepository source", Short: "Delete an OCIRepository source",
Long: withPreviewNote("The delete source oci command deletes the given OCIRepository from the cluster."), Long: "The delete source oci command deletes the given OCIRepository from the cluster.",
Example: ` # Delete an OCIRepository Example: ` # Delete an OCIRepository
flux delete source oci podinfo`, flux delete source oci podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)), ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),

View File

@@ -23,7 +23,7 @@ import (
var diffCmd = &cobra.Command{ var diffCmd = &cobra.Command{
Use: "diff", Use: "diff",
Short: "Diff a flux resource", Short: "Diff a flux resource",
Long: `The diff command is used to do a server-side dry-run on flux resources, then prints the diff.`, Long: "The diff command is used to do a server-side dry-run on flux resources, then prints the diff.",
} }
func init() { func init() {

View File

@@ -1,112 +0,0 @@
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"os"
oci "github.com/fluxcd/pkg/oci/client"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/flags"
)
var diffArtifactCmd = &cobra.Command{
Use: "artifact",
Short: "Diff Artifact",
Long: withPreviewNote(`The diff artifact command computes the diff between the remote OCI artifact and a local directory or file`),
Example: `# Check if local files differ from remote
flux diff artifact oci://ghcr.io/stefanprodan/manifests:podinfo:6.2.0 --path=./kustomize`,
RunE: diffArtifactCmdRun,
}
type diffArtifactFlags struct {
path string
creds string
provider flags.SourceOCIProvider
ignorePaths []string
}
var diffArtifactArgs = newDiffArtifactArgs()
func newDiffArtifactArgs() diffArtifactFlags {
return diffArtifactFlags{
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
}
}
func init() {
diffArtifactCmd.Flags().StringVar(&diffArtifactArgs.path, "path", "", "path to the directory where the Kubernetes manifests are located")
diffArtifactCmd.Flags().StringVar(&diffArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
diffArtifactCmd.Flags().Var(&diffArtifactArgs.provider, "provider", sourceOCIRepositoryArgs.provider.Description())
diffArtifactCmd.Flags().StringSliceVar(&diffArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
diffCmd.AddCommand(diffArtifactCmd)
}
func diffArtifactCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("artifact URL is required")
}
ociURL := args[0]
if diffArtifactArgs.path == "" {
return fmt.Errorf("invalid path %q", diffArtifactArgs.path)
}
url, err := oci.ParseArtifactURL(ociURL)
if err != nil {
return err
}
if _, err := os.Stat(diffArtifactArgs.path); err != nil {
return fmt.Errorf("invalid path '%s', must point to an existing directory or file", diffArtifactArgs.path)
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
ociClient := oci.NewClient(oci.DefaultOptions())
if diffArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && diffArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials")
if err := ociClient.LoginWithCredentials(diffArtifactArgs.creds); err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
}
}
if diffArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials")
ociProvider, err := diffArtifactArgs.provider.ToOCIProvider()
if err != nil {
return fmt.Errorf("provider not supported: %w", err)
}
if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
return fmt.Errorf("error during login with provider: %w", err)
}
}
if err := ociClient.Diff(ctx, url, diffArtifactArgs.path, diffArtifactArgs.ignorePaths); err != nil {
return err
}
logger.Successf("no changes detected")
return nil
}

View File

@@ -1,109 +0,0 @@
//go:build unit
// +build unit
/*
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 (
"context"
"fmt"
"testing"
"time"
"github.com/distribution/distribution/v3/configuration"
"github.com/distribution/distribution/v3/registry"
_ "github.com/distribution/distribution/v3/registry/auth/htpasswd"
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
"github.com/phayes/freeport"
ctrl "sigs.k8s.io/controller-runtime"
)
var dockerReg string
func setupRegistryServer(ctx context.Context) error {
// Registry config
config := &configuration.Configuration{}
port, err := freeport.GetFreePort()
if err != nil {
return fmt.Errorf("failed to get free port: %s", err)
}
dockerReg = fmt.Sprintf("localhost:%d", port)
config.HTTP.Addr = fmt.Sprintf("127.0.0.1:%d", port)
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
dockerRegistry, err := registry.NewRegistry(ctx, config)
if err != nil {
return fmt.Errorf("failed to create docker registry: %w", err)
}
// Start Docker registry
go dockerRegistry.ListenAndServe()
return nil
}
func TestDiffArtifact(t *testing.T) {
tests := []struct {
name string
url string
argsTpl string
pushFile string
diffFile string
assert assertFunc
}{
{
name: "should not fail if there is no diff",
url: "oci://%s/podinfo:1.0.0",
argsTpl: "diff artifact %s --path=%s",
pushFile: "./testdata/diff-artifact/deployment.yaml",
diffFile: "./testdata/diff-artifact/deployment.yaml",
assert: assertGoldenFile("testdata/diff-artifact/success.golden"),
},
{
name: "should fail if there is a diff",
url: "oci://%s/podinfo:2.0.0",
argsTpl: "diff artifact %s --path=%s",
pushFile: "./testdata/diff-artifact/deployment.yaml",
diffFile: "./testdata/diff-artifact/deployment-diff.yaml",
assert: assertError("the remote artifact contents differs from the local one"),
},
}
ctx := ctrl.SetupSignalHandler()
err := setupRegistryServer(ctx)
if err != nil {
panic(fmt.Sprintf("failed to start docker registry: %s", err))
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.url = fmt.Sprintf(tt.url, dockerReg)
_, err := executeCommand("push artifact " + tt.url + " --path=" + tt.pushFile + " --source=test --revision=test")
if err != nil {
t.Fatalf(fmt.Errorf("failed to push image: %w", err).Error())
}
cmd := cmdTestCase{
args: fmt.Sprintf(tt.argsTpl, tt.url, tt.diffFile),
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}

View File

@@ -23,8 +23,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/flux2/v2/internal/build" "github.com/fluxcd/flux2/internal/build"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
) )
var diffKsCmd = &cobra.Command{ var diffKsCmd = &cobra.Command{
@@ -37,13 +37,7 @@ Exit status: 0 No differences were found. 1 Differences were found. >1 diff fail
flux diff kustomization my-app --path ./path/to/local/manifests flux diff kustomization my-app --path ./path/to/local/manifests
# Preview using a local flux kustomization file # Preview using a local flux kustomization file
flux diff kustomization my-app --path ./path/to/local/manifests \ flux diff kustomization my-app --path ./path/to/local/manifests --kustomization-file ./path/to/local/my-app.yaml`,
--kustomization-file ./path/to/local/my-app.yaml
# Exclude files by providing a comma separated list of entries that follow the .gitignore pattern fromat.
flux diff kustomization my-app --path ./path/to/local/manifests \
--kustomization-file ./path/to/local/my-app.yaml \
--ignore-paths "/to_ignore/**/*.yaml,ignore.yaml"`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: diffKsCmdRun, RunE: diffKsCmdRun,
} }
@@ -51,7 +45,6 @@ flux diff kustomization my-app --path ./path/to/local/manifests \
type diffKsFlags struct { type diffKsFlags struct {
kustomizationFile string kustomizationFile string
path string path string
ignorePaths []string
progressBar bool progressBar bool
} }
@@ -60,7 +53,6 @@ var diffKsArgs diffKsFlags
func init() { func init() {
diffKsCmd.Flags().StringVar(&diffKsArgs.path, "path", "", "Path to a local directory that matches the specified Kustomization.spec.path.") diffKsCmd.Flags().StringVar(&diffKsArgs.path, "path", "", "Path to a local directory that matches the specified Kustomization.spec.path.")
diffKsCmd.Flags().BoolVar(&diffKsArgs.progressBar, "progress-bar", true, "Boolean to set the progress bar. The default value is true.") diffKsCmd.Flags().BoolVar(&diffKsArgs.progressBar, "progress-bar", true, "Boolean to set the progress bar. The default value is true.")
diffKsCmd.Flags().StringSliceVar(&diffKsArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in .gitignore format")
diffKsCmd.Flags().StringVar(&diffKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.") diffKsCmd.Flags().StringVar(&diffKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.")
diffCmd.AddCommand(diffKsCmd) diffCmd.AddCommand(diffKsCmd)
} }
@@ -85,25 +77,12 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
var ( var builder *build.Builder
builder *build.Builder var err error
err error
)
if diffKsArgs.progressBar { if diffKsArgs.progressBar {
builder, err = build.NewBuilder(name, diffKsArgs.path, builder, err = build.NewBuilder(kubeconfigArgs, kubeclientOptions, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout), build.WithKustomizationFile(diffKsArgs.kustomizationFile), build.WithProgressBar())
build.WithClientConfig(kubeconfigArgs, kubeclientOptions),
build.WithTimeout(rootArgs.timeout),
build.WithKustomizationFile(diffKsArgs.kustomizationFile),
build.WithProgressBar(),
build.WithIgnore(diffKsArgs.ignorePaths),
)
} else { } else {
builder, err = build.NewBuilder(name, diffKsArgs.path, builder, err = build.NewBuilder(kubeconfigArgs, kubeclientOptions, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout), build.WithKustomizationFile(diffKsArgs.kustomizationFile))
build.WithClientConfig(kubeconfigArgs, kubeclientOptions),
build.WithTimeout(rootArgs.timeout),
build.WithKustomizationFile(diffKsArgs.kustomizationFile),
build.WithIgnore(diffKsArgs.ignorePaths),
)
} }
if err != nil { if err != nil {

View File

@@ -25,7 +25,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/fluxcd/flux2/v2/internal/build" "github.com/fluxcd/flux2/internal/build"
"github.com/fluxcd/pkg/ssa" "github.com/fluxcd/pkg/ssa"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
) )
@@ -97,7 +97,7 @@ func TestDiffKustomization(t *testing.T) {
"fluxns": allocateNamespace("flux-system"), "fluxns": allocateNamespace("flux-system"),
} }
b, _ := build.NewBuilder("podinfo", "", build.WithClientConfig(kubeconfigArgs, kubeclientOptions)) b, _ := build.NewBuilder(kubeconfigArgs, kubeclientOptions, "podinfo", "")
resourceManager, err := b.Manager() resourceManager, err := b.Manager()
if err != nil { if err != nil {
@@ -109,9 +109,7 @@ func TestDiffKustomization(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if tt.objectFile != "" { if tt.objectFile != "" {
if _, err := resourceManager.ApplyAll(context.Background(), createObjectFromFile(tt.objectFile, tmpl, t), ssa.DefaultApplyOptions()); err != nil { resourceManager.ApplyAll(context.Background(), createObjectFromFile(tt.objectFile, tmpl, t), ssa.DefaultApplyOptions())
t.Error(err)
}
} }
cmd := cmdTestCase{ cmd := cmdTestCase{
args: tt.args + " -n " + tmpl["fluxns"], args: tt.args + " -n " + tmpl["fluxns"],

View File

@@ -1,501 +0,0 @@
/*
Copyright 2023 The Kubernetes Authors.
Copyright 2023 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"os"
"sort"
"strings"
"time"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/watch"
runtimeresource "k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/controller-runtime/pkg/client"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/utils"
"github.com/fluxcd/flux2/v2/pkg/printers"
)
var eventsCmd = &cobra.Command{
Use: "events",
Short: "Display Kubernetes events for Flux resources",
Long: withPreviewNote("The events sub-command shows Kubernetes events from Flux resources"),
Example: ` # Display events for flux resources in default namespace
flux events -n default
# Display events for flux resources in all namespaces
flux events -A
# Display events for flux resources
flux events --for Kustomization/podinfo
`,
RunE: eventsCmdRun,
}
type eventFlags struct {
allNamespaces bool
watch bool
forSelector string
filterTypes []string
}
var eventArgs eventFlags
func init() {
eventsCmd.Flags().BoolVarP(&eventArgs.allNamespaces, "all-namespaces", "A", false,
"display events from Flux resources across all namespaces")
eventsCmd.Flags().BoolVarP(&eventArgs.watch, "watch", "w", false,
"indicate if the events should be streamed")
eventsCmd.Flags().StringVar(&eventArgs.forSelector, "for", "",
"get events for a particular object")
eventsCmd.Flags().StringSliceVar(&eventArgs.filterTypes, "types", []string{}, "filter events for certain types")
rootCmd.AddCommand(eventsCmd)
}
func eventsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeclient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
namespace := *kubeconfigArgs.Namespace
if eventArgs.allNamespaces {
namespace = ""
}
var diffRefNs bool
clientListOpts := getListOpt(namespace, eventArgs.forSelector)
var refListOpts [][]client.ListOption
if eventArgs.forSelector != "" {
refs, err := getObjectRef(ctx, kubeclient, eventArgs.forSelector, *kubeconfigArgs.Namespace)
if err != nil {
return err
}
for _, ref := range refs {
kind, name, refNs := utils.ParseObjectKindNameNamespace(ref)
if refNs != namespace {
diffRefNs = true
}
refSelector := fmt.Sprintf("%s/%s", kind, name)
refListOpts = append(refListOpts, getListOpt(refNs, refSelector))
}
}
showNamespace := namespace == "" || diffRefNs
if eventArgs.watch {
return eventsCmdWatchRun(ctx, kubeclient, clientListOpts, refListOpts, showNamespace)
}
rows, err := getRows(ctx, kubeclient, clientListOpts, refListOpts, showNamespace)
if len(rows) == 0 {
if eventArgs.allNamespaces {
logger.Failuref("No events found.")
} else {
logger.Failuref("No events found in %s namespace.", *kubeconfigArgs.Namespace)
}
return nil
}
headers := getHeaders(showNamespace)
err = printers.TablePrinter(headers).Print(cmd.OutOrStdout(), rows)
return err
}
func getRows(ctx context.Context, kubeclient client.Client, clientListOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) ([][]string, error) {
el := &corev1.EventList{}
if err := addEventsToList(ctx, kubeclient, el, clientListOpts); err != nil {
return nil, err
}
for _, refOpts := range refListOpts {
if err := addEventsToList(ctx, kubeclient, el, refOpts); err != nil {
return nil, err
}
}
sort.Sort(SortableEvents(el.Items))
var rows [][]string
for _, item := range el.Items {
if ignoreEvent(item) {
continue
}
rows = append(rows, getEventRow(item, showNs))
}
return rows, nil
}
func addEventsToList(ctx context.Context, kubeclient client.Client, el *corev1.EventList, clientListOpts []client.ListOption) error {
listOpts := &metav1.ListOptions{}
err := runtimeresource.FollowContinue(listOpts,
func(options metav1.ListOptions) (runtime.Object, error) {
newEvents := &corev1.EventList{}
err := kubeclient.List(ctx, newEvents, clientListOpts...)
if err != nil {
return nil, fmt.Errorf("error getting events: %w", err)
}
el.Items = append(el.Items, newEvents.Items...)
return newEvents, nil
})
return err
}
func getListOpt(namespace, selector string) []client.ListOption {
clientListOpts := []client.ListOption{client.Limit(cmdutil.DefaultChunkSize), client.InNamespace(namespace)}
if selector != "" {
kind, name := utils.ParseObjectKindName(selector)
sel := fields.AndSelectors(
fields.OneTermEqualSelector("involvedObject.kind", kind),
fields.OneTermEqualSelector("involvedObject.name", name))
clientListOpts = append(clientListOpts, client.MatchingFieldsSelector{Selector: sel})
}
return clientListOpts
}
func eventsCmdWatchRun(ctx context.Context, kubeclient client.WithWatch, listOpts []client.ListOption, refListOpts [][]client.ListOption, showNs bool) error {
event := &corev1.EventList{}
eventWatch, err := kubeclient.Watch(ctx, event, listOpts...)
if err != nil {
return err
}
firstIteration := true
handleEvent := func(e watch.Event) error {
if e.Type == watch.Deleted {
return nil
}
event, ok := e.Object.(*corev1.Event)
if !ok {
return nil
}
if ignoreEvent(*event) {
return nil
}
rows := getEventRow(*event, showNs)
var hdr []string
if firstIteration {
hdr = getHeaders(showNs)
firstIteration = false
}
err = printers.TablePrinter(hdr).Print(os.Stdout, [][]string{rows})
if err != nil {
return err
}
return nil
}
for _, refOpts := range refListOpts {
refEventWatch, err := kubeclient.Watch(ctx, event, refOpts...)
if err != nil {
return err
}
go func() {
err := receiveEventChan(ctx, refEventWatch, handleEvent)
if err != nil {
logger.Failuref("error watching events: %s", err.Error())
}
}()
}
return receiveEventChan(ctx, eventWatch, handleEvent)
}
func receiveEventChan(ctx context.Context, eventWatch watch.Interface, f func(e watch.Event) error) error {
defer eventWatch.Stop()
for {
select {
case e, ok := <-eventWatch.ResultChan():
if !ok {
return nil
}
err := f(e)
if err != nil {
return err
}
case <-ctx.Done():
return nil
}
}
}
func getHeaders(showNs bool) []string {
headers := []string{"Last seen", "Type", "Reason", "Object", "Message"}
if showNs {
headers = append(namespaceHeader, headers...)
}
return headers
}
func getEventRow(e corev1.Event, showNs bool) []string {
var row []string
if showNs {
row = []string{e.Namespace}
}
row = append(row, getLastSeen(e), e.Type, e.Reason, fmt.Sprintf("%s/%s", e.InvolvedObject.Kind, e.InvolvedObject.Name), e.Message)
return row
}
// getObjectRef is used to get the metadata of a resource that the selector(in the format <kind/name>) references.
// It returns an empty string if the resource doesn't reference any resource
// and a string with the format `<kind>/<name>.<namespace>` if it does.
func getObjectRef(ctx context.Context, kubeclient client.Client, selector string, ns string) ([]string, error) {
kind, name := utils.ParseObjectKindName(selector)
ref, err := fluxKindMap.getRefInfo(kind)
if err != nil {
return nil, fmt.Errorf("error getting groupversion: %w", err)
}
// the resource has no source ref
if len(ref.field) == 0 {
return nil, nil
}
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(schema.GroupVersionKind{
Kind: kind,
Version: ref.gv.Version,
Group: ref.gv.Group,
})
objName := types.NamespacedName{
Namespace: ns,
Name: name,
}
err = kubeclient.Get(ctx, objName, obj)
if err != nil {
return nil, err
}
var ok bool
refKind := ref.kind
if refKind == "" {
kindField := append(ref.field, "kind")
refKind, ok, err = unstructured.NestedString(obj.Object, kindField...)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("field '%s' for '%s' not found", strings.Join(kindField, "."), objName)
}
}
nameField := append(ref.field, "name")
refName, ok, err := unstructured.NestedString(obj.Object, nameField...)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("field '%s' for '%s' not found", strings.Join(nameField, "."), objName)
}
var allRefs []string
refNamespace := ns
if ref.crossNamespaced {
namespaceField := append(ref.field, "namespace")
namespace, ok, err := unstructured.NestedString(obj.Object, namespaceField...)
if err != nil {
return nil, err
}
if ok {
refNamespace = namespace
}
}
allRefs = append(allRefs, fmt.Sprintf("%s/%s.%s", refKind, refName, refNamespace))
if ref.otherRefs != nil {
for _, otherRef := range ref.otherRefs(ns, name) {
allRefs = append(allRefs, fmt.Sprintf("%s.%s", otherRef, refNamespace))
}
}
return allRefs, nil
}
type refMap map[string]refInfo
func (r refMap) getRefInfo(kind string) (refInfo, error) {
for key, ref := range r {
if strings.EqualFold(key, kind) {
return ref, nil
}
}
return refInfo{}, fmt.Errorf("'%s' is not a recognized Flux kind", kind)
}
func (r refMap) hasKind(kind string) bool {
_, err := r.getRefInfo(kind)
return err == nil
}
type refInfo struct {
gv schema.GroupVersion
kind string
crossNamespaced bool
otherRefs func(namespace, name string) []string
field []string
}
var fluxKindMap = refMap{
kustomizev1.KustomizationKind: {
gv: kustomizev1.GroupVersion,
crossNamespaced: true,
field: []string{"spec", "sourceRef"},
},
helmv2.HelmReleaseKind: {
gv: helmv2.GroupVersion,
crossNamespaced: true,
otherRefs: func(namespace, name string) []string {
return []string{fmt.Sprintf("%s/%s-%s", sourcev1b2.HelmChartKind, namespace, name)}
},
field: []string{"spec", "chart", "spec", "sourceRef"},
},
notificationv1b2.AlertKind: {
gv: notificationv1b2.GroupVersion,
kind: notificationv1b2.ProviderKind,
crossNamespaced: false,
field: []string{"spec", "providerRef"},
},
notificationv1.ReceiverKind: {gv: notificationv1.GroupVersion},
notificationv1b2.ProviderKind: {gv: notificationv1b2.GroupVersion},
imagev1.ImagePolicyKind: {
gv: imagev1.GroupVersion,
kind: imagev1.ImageRepositoryKind,
crossNamespaced: true,
field: []string{"spec", "imageRepositoryRef"},
},
sourcev1.GitRepositoryKind: {gv: sourcev1.GroupVersion},
sourcev1b2.OCIRepositoryKind: {gv: sourcev1b2.GroupVersion},
sourcev1b2.BucketKind: {gv: sourcev1b2.GroupVersion},
sourcev1b2.HelmRepositoryKind: {gv: sourcev1b2.GroupVersion},
sourcev1b2.HelmChartKind: {gv: sourcev1b2.GroupVersion},
autov1.ImageUpdateAutomationKind: {gv: autov1.GroupVersion},
imagev1.ImageRepositoryKind: {gv: imagev1.GroupVersion},
}
func ignoreEvent(e corev1.Event) bool {
if !fluxKindMap.hasKind(e.InvolvedObject.Kind) {
return true
}
if len(eventArgs.filterTypes) > 0 {
_, equal := utils.ContainsEqualFoldItemString(eventArgs.filterTypes, e.Type)
if !equal {
return true
}
}
return false
}
// The functions below are copied from: https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/events/events.go#L347
// SortableEvents implements sort.Interface for []api.Event by time
type SortableEvents []corev1.Event
func (list SortableEvents) Len() int {
return len(list)
}
func (list SortableEvents) Swap(i, j int) {
list[i], list[j] = list[j], list[i]
}
// Return the time that should be used for sorting, which can come from
// various places in corev1.Event.
func eventTime(event corev1.Event) time.Time {
if event.Series != nil {
return event.Series.LastObservedTime.Time
}
if !event.LastTimestamp.Time.IsZero() {
return event.LastTimestamp.Time
}
return event.EventTime.Time
}
func (list SortableEvents) Less(i, j int) bool {
return eventTime(list[i]).Before(eventTime(list[j]))
}
func getLastSeen(e corev1.Event) string {
var interval string
firstTimestampSince := translateMicroTimestampSince(e.EventTime)
if e.EventTime.IsZero() {
firstTimestampSince = translateTimestampSince(e.FirstTimestamp)
}
if e.Series != nil {
interval = fmt.Sprintf("%s (x%d over %s)", translateMicroTimestampSince(e.Series.LastObservedTime), e.Series.Count, firstTimestampSince)
} else if e.Count > 1 {
interval = fmt.Sprintf("%s (x%d over %s)", translateTimestampSince(e.LastTimestamp), e.Count, firstTimestampSince)
} else {
interval = firstTimestampSince
}
return interval
}
// translateMicroTimestampSince returns the elapsed time since timestamp in
// human-readable approximation.
func translateMicroTimestampSince(timestamp metav1.MicroTime) string {
if timestamp.IsZero() {
return "<unknown>"
}
return duration.HumanDuration(time.Since(timestamp.Time))
}
// translateTimestampSince returns the elapsed time since timestamp in
// human-readable approximation.
func translateTimestampSince(timestamp metav1.Time) string {
if timestamp.IsZero() {
return "<unknown>"
}
return duration.HumanDuration(time.Since(timestamp.Time))
}

View File

@@ -1,417 +0,0 @@
/*
Copyright 2023 The Kubernetes Authors.
Copyright 2023 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"strings"
"testing"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
helmv2beta1 "github.com/fluxcd/helm-controller/api/v2beta1"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
"github.com/fluxcd/pkg/ssa"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/utils"
)
var objects = `
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: flux-system
namespace: flux-system
spec:
interval: 5m0s
path: ./infrastructure/
prune: true
sourceRef:
kind: GitRepository
name: flux-system
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: podinfo
namespace: default
spec:
interval: 5m0s
path: ./infrastructure/
prune: true
sourceRef:
kind: GitRepository
name: flux-system
namespace: flux-system
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: flux-system
namespace: flux-system
spec:
interval: 5m0s
ref:
branch: main
secretRef:
name: flux-system
timeout: 1m0s
url: ssh://git@github.com/example/repo
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: podinfo
namespace: default
spec:
chart:
spec:
chart: podinfo
reconcileStrategy: ChartVersion
sourceRef:
kind: HelmRepository
name: podinfo
namespace: flux-system
version: '*'
interval: 5m0s
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: podinfo
namespace: flux-system
spec:
interval: 1m0s
url: https://stefanprodan.github.io/podinfo
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmChart
metadata:
name: default-podinfo
namespace: flux-system
spec:
chart: podinfo
interval: 1m0s
reconcileStrategy: ChartVersion
sourceRef:
kind: HelmRepository
name: podinfo-chart
version: '*'
---
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Alert
metadata:
name: webapp
namespace: flux-system
spec:
eventSeverity: info
eventSources:
- kind: GitRepository
name: '*'
providerRef:
name: slack
---
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
address: https://hooks.slack.com/services/mock
type: slack
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: podinfo
namespace: default
spec:
imageRepositoryRef:
name: acr-podinfo
namespace: flux-system
policy:
semver:
range: 5.0.x
---
apiVersion: v1
kind: Namespace
metadata:
name: flux-system`
func Test_getObjectRef(t *testing.T) {
g := NewWithT(t)
objs, err := ssa.ReadObjects(strings.NewReader(objects))
g.Expect(err).To(Not(HaveOccurred()))
builder := fake.NewClientBuilder().WithScheme(getScheme())
for _, obj := range objs {
builder = builder.WithObjects(obj)
}
c := builder.Build()
tests := []struct {
name string
selector string
namespace string
want []string
wantErr bool
}{
{
name: "Source Ref for Kustomization",
selector: "Kustomization/flux-system",
namespace: "flux-system",
want: []string{"GitRepository/flux-system.flux-system"},
},
{
name: "Crossnamespace Source Ref for Kustomization",
selector: "Kustomization/podinfo",
namespace: "default",
want: []string{"GitRepository/flux-system.flux-system"},
},
{
name: "Source Ref for HelmRelease",
selector: "HelmRelease/podinfo",
namespace: "default",
want: []string{"HelmRepository/podinfo.flux-system", "HelmChart/default-podinfo.flux-system"},
},
{
name: "Source Ref for Alert",
selector: "Alert/webapp",
namespace: "flux-system",
want: []string{"Provider/slack.flux-system"},
},
{
name: "Source Ref for ImagePolicy",
selector: "ImagePolicy/podinfo",
namespace: "default",
want: []string{"ImageRepository/acr-podinfo.flux-system"},
},
{
name: "Empty Ref for Provider",
selector: "Provider/slack",
namespace: "flux-system",
want: nil,
},
{
name: "Non flux resource",
selector: "Namespace/flux-system",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
got, err := getObjectRef(context.Background(), c, tt.selector, tt.namespace)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
return
}
g.Expect(err).To(Not(HaveOccurred()))
g.Expect(got).To(Equal(tt.want))
})
}
}
func Test_getRows(t *testing.T) {
g := NewWithT(t)
objs, err := ssa.ReadObjects(strings.NewReader(objects))
g.Expect(err).To(Not(HaveOccurred()))
builder := fake.NewClientBuilder().WithScheme(getScheme())
for _, obj := range objs {
builder = builder.WithObjects(obj)
}
eventList := &corev1.EventList{}
for _, obj := range objs {
infoEvent := createEvent(obj, eventv1.EventSeverityInfo, "Info Message", "Info Reason")
warningEvent := createEvent(obj, eventv1.EventSeverityError, "Error Message", "Error Reason")
eventList.Items = append(eventList.Items, infoEvent, warningEvent)
}
builder = builder.WithLists(eventList)
builder.WithIndex(&corev1.Event{}, "involvedObject.kind/name", kindNameIndexer)
c := builder.Build()
tests := []struct {
name string
selector string
refSelector string
namespace string
refNs string
expected [][]string
}{
{
name: "events from all namespaces",
selector: "",
namespace: "",
expected: [][]string{
{"default", "<unknown>", "error", "Error Reason", "HelmRelease/podinfo", "Error Message"},
{"default", "<unknown>", "info", "Info Reason", "HelmRelease/podinfo", "Info Message"},
{"default", "<unknown>", "error", "Error Reason", "ImagePolicy/podinfo", "Error Message"},
{"default", "<unknown>", "info", "Info Reason", "ImagePolicy/podinfo", "Info Message"},
{"default", "<unknown>", "error", "Error Reason", "Kustomization/podinfo", "Error Message"},
{"default", "<unknown>", "info", "Info Reason", "Kustomization/podinfo", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "Alert/webapp", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "Alert/webapp", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "GitRepository/flux-system", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "GitRepository/flux-system", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "HelmChart/default-podinfo", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "HelmChart/default-podinfo", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "HelmRepository/podinfo", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "HelmRepository/podinfo", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "Kustomization/flux-system", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "Kustomization/flux-system", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "Provider/slack", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "Provider/slack", "Info Message"},
},
},
{
name: "events from default namespaces",
selector: "",
namespace: "default",
expected: [][]string{
{"<unknown>", "error", "Error Reason", "HelmRelease/podinfo", "Error Message"},
{"<unknown>", "info", "Info Reason", "HelmRelease/podinfo", "Info Message"},
{"<unknown>", "error", "Error Reason", "ImagePolicy/podinfo", "Error Message"},
{"<unknown>", "info", "Info Reason", "ImagePolicy/podinfo", "Info Message"},
{"<unknown>", "error", "Error Reason", "Kustomization/podinfo", "Error Message"},
{"<unknown>", "info", "Info Reason", "Kustomization/podinfo", "Info Message"},
},
},
{
name: "Kustomization with crossnamespaced GitRepository",
selector: "Kustomization/podinfo",
namespace: "default",
expected: [][]string{
{"default", "<unknown>", "error", "Error Reason", "Kustomization/podinfo", "Error Message"},
{"default", "<unknown>", "info", "Info Reason", "Kustomization/podinfo", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "GitRepository/flux-system", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "GitRepository/flux-system", "Info Message"},
},
},
{
name: "HelmRelease with crossnamespaced HelmRepository",
selector: "HelmRelease/podinfo",
namespace: "default",
expected: [][]string{
{"default", "<unknown>", "error", "Error Reason", "HelmRelease/podinfo", "Error Message"},
{"default", "<unknown>", "info", "Info Reason", "HelmRelease/podinfo", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "HelmRepository/podinfo", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "HelmRepository/podinfo", "Info Message"},
{"flux-system", "<unknown>", "error", "Error Reason", "HelmChart/default-podinfo", "Error Message"},
{"flux-system", "<unknown>", "info", "Info Reason", "HelmChart/default-podinfo", "Info Message"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
var refs []string
var refNs, refKind, refName string
if tt.selector != "" {
refs, err = getObjectRef(context.Background(), c, tt.selector, tt.namespace)
g.Expect(err).To(Not(HaveOccurred()))
}
g.Expect(err).To(Not(HaveOccurred()))
clientOpts := getTestListOpt(tt.namespace, tt.selector)
var refOpts [][]client.ListOption
for _, ref := range refs {
refKind, refName, refNs = utils.ParseObjectKindNameNamespace(ref)
refSelector := fmt.Sprintf("%s/%s", refKind, refName)
refOpts = append(refOpts, getTestListOpt(refNs, refSelector))
}
showNs := tt.namespace == "" || (refNs != "" && refNs != tt.namespace)
rows, err := getRows(context.Background(), c, clientOpts, refOpts, showNs)
g.Expect(err).To(Not(HaveOccurred()))
g.Expect(rows).To(Equal(tt.expected))
})
}
}
func getTestListOpt(namespace, selector string) []client.ListOption {
clientListOpts := []client.ListOption{client.Limit(cmdutil.DefaultChunkSize), client.InNamespace(namespace)}
if selector != "" {
sel := fields.OneTermEqualSelector("involvedObject.kind/name", selector)
clientListOpts = append(clientListOpts, client.MatchingFieldsSelector{Selector: sel})
}
return clientListOpts
}
func getScheme() *runtime.Scheme {
newscheme := runtime.NewScheme()
corev1.AddToScheme(newscheme)
kustomizev1.AddToScheme(newscheme)
helmv2beta1.AddToScheme(newscheme)
notificationv1.AddToScheme(newscheme)
notificationv1b2.AddToScheme(newscheme)
imagev1.AddToScheme(newscheme)
autov1.AddToScheme(newscheme)
sourcev1.AddToScheme(newscheme)
sourcev1b2.AddToScheme(newscheme)
return newscheme
}
func createEvent(obj client.Object, eventType, msg, reason string) corev1.Event {
return corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Namespace: obj.GetNamespace(),
// name of event needs to be unique so fak
Name: obj.GetNamespace() + obj.GetNamespace() + obj.GetObjectKind().GroupVersionKind().Kind + eventType,
},
Reason: reason,
Message: msg,
Type: eventType,
InvolvedObject: corev1.ObjectReference{
Kind: obj.GetObjectKind().GroupVersionKind().Kind,
Namespace: obj.GetNamespace(),
Name: obj.GetName(),
},
}
}
func kindNameIndexer(obj client.Object) []string {
e, ok := obj.(*corev1.Event)
if !ok {
panic(fmt.Sprintf("Expected a Event, got %T", e))
}
return []string{fmt.Sprintf("%s/%s", e.InvolvedObject.Kind, e.InvolvedObject.Name)}
}

View File

@@ -26,13 +26,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
var exportCmd = &cobra.Command{ var exportCmd = &cobra.Command{
Use: "export", Use: "export",
Short: "Export resources in YAML format", Short: "Export resources in YAML format",
Long: `The export sub-commands export resources in YAML format.`, Long: "The export sub-commands export resources in YAML format.",
} }
type exportFlags struct { type exportFlags struct {
@@ -122,6 +122,5 @@ func printExport(export interface{}) error {
func resourceToString(data []byte) string { func resourceToString(data []byte) string {
data = bytes.Replace(data, []byte(" creationTimestamp: null\n"), []byte(""), 1) data = bytes.Replace(data, []byte(" creationTimestamp: null\n"), []byte(""), 1)
data = bytes.Replace(data, []byte("status: {}\n"), []byte(""), 1) data = bytes.Replace(data, []byte("status: {}\n"), []byte(""), 1)
data = bytes.TrimSpace(data)
return string(data) return string(data)
} }

View File

@@ -20,13 +20,13 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var exportAlertCmd = &cobra.Command{ var exportAlertCmd = &cobra.Command{
Use: "alert [name]", Use: "alert [name]",
Short: "Export Alert resources in YAML format", Short: "Export Alert resources in YAML format",
Long: withPreviewNote("The export alert command exports one or all Alert resources in YAML format."), Long: "The export alert command exports one or all Alert resources in YAML format.",
Example: ` # Export all Alert resources Example: ` # Export all Alert resources
flux export alert --all > alerts.yaml flux export alert --all > alerts.yaml

View File

@@ -20,13 +20,13 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var exportAlertProviderCmd = &cobra.Command{ var exportAlertProviderCmd = &cobra.Command{
Use: "alert-provider [name]", Use: "alert-provider [name]",
Short: "Export Provider resources in YAML format", Short: "Export Provider resources in YAML format",
Long: withPreviewNote("The export alert-provider command exports one or all Provider resources in YAML format."), Long: "The export alert-provider command exports one or all Provider resources in YAML format.",
Example: ` # Export all Provider resources Example: ` # Export all Provider resources
flux export alert-provider --all > alert-providers.yaml flux export alert-provider --all > alert-providers.yaml

View File

@@ -27,7 +27,7 @@ var exportHelmReleaseCmd = &cobra.Command{
Use: "helmrelease [name]", Use: "helmrelease [name]",
Aliases: []string{"hr"}, Aliases: []string{"hr"},
Short: "Export HelmRelease resources in YAML format", Short: "Export HelmRelease resources in YAML format",
Long: withPreviewNote("The export helmrelease command exports one or all HelmRelease resources in YAML format."), Long: "The export helmrelease command exports one or all HelmRelease resources in YAML format.",
Example: ` # Export all HelmRelease resources Example: ` # Export all HelmRelease resources
flux export helmrelease --all > kustomizations.yaml flux export helmrelease --all > kustomizations.yaml

View File

@@ -23,7 +23,7 @@ import (
var exportImageCmd = &cobra.Command{ var exportImageCmd = &cobra.Command{
Use: "image", Use: "image",
Short: "Export image automation objects", Short: "Export image automation objects",
Long: `The export image sub-commands export image automation objects in YAML format.`, Long: "The export image sub-commands export image automation objects in YAML format.",
} }
func init() { func init() {

View File

@@ -20,13 +20,13 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
) )
var exportImagePolicyCmd = &cobra.Command{ var exportImagePolicyCmd = &cobra.Command{
Use: "policy [name]", Use: "policy [name]",
Short: "Export ImagePolicy resources in YAML format", Short: "Export ImagePolicy resources in YAML format",
Long: withPreviewNote("The export image policy command exports one or all ImagePolicy resources in YAML format."), Long: "The export image policy command exports one or all ImagePolicy resources in YAML format.",
Example: ` # Export all ImagePolicy resources Example: ` # Export all ImagePolicy resources
flux export image policy --all > image-policies.yaml flux export image policy --all > image-policies.yaml

Some files were not shown because too many files have changed in this diff Show More