1
0
mirror of synced 2026-03-02 03:26:57 +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
186 changed files with 3734 additions and 4509 deletions

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,39 +1,34 @@
name: e2e-bootstrap name: bootstrap
on: on:
workflow_dispatch:
push: push:
branches: [ main ] branches: [ main ]
pull_request: pull_request:
branches: [ main ] 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@ac593985615ec2ede58e132d2e21d2b1cbd6127c uses: actions/checkout@v3
- name: Restore Go cache - name: Restore Go cache
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 uses: actions/cache@v3
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
restore-keys: | restore-keys: |
${{ runner.os }}-go1.18- ${{ runner.os }}-go1.18-
- name: Setup Go - name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 uses: actions/setup-go@v3
with: with:
go-version: 1.19.x go-version: 1.19.x
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@d8ccf8fb623ce1bb360ae2f45f323d9d5c5e9f00 # v1.5.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
node_image: kindest/node:v1.25.2
- name: Setup Kustomize - name: Setup Kustomize
uses: fluxcd/pkg//actions/kustomize@main uses: fluxcd/pkg//actions/kustomize@main
- name: Build - name: Build
@@ -48,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/ \
@@ -85,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,97 +3,33 @@ name: e2e-arm64
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: [ main, update-components, e2e-arm64* ] 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
KUBERNETES_VERSION: [ 1.23.13, 1.24.7, 1.25.3 ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c uses: actions/checkout@v3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 uses: actions/setup-go@v3
with: with:
go-version: 1.19.x go-version: 1.19.x
- 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

@@ -7,26 +7,31 @@ on:
push: push:
branches: [ azure* ] branches: [ azure* ]
permissions:
contents: read
jobs: jobs:
e2e-amd64-aks: e2e:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c uses: actions/checkout@v3
- name: Restore Go cache - name: Restore Go cache
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 uses: actions/cache@v3
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
restore-keys: | restore-keys: |
${{ runner.os }}-go1.18- ${{ runner.os }}-go1.18-
- name: Setup Go - name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 uses: actions/setup-go@v2
with: with:
go-version: 1.19.x 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
@@ -39,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 ] branches: [ main ]
pull_request: pull_request:
branches: [ main, oci ] 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,25 +16,24 @@ jobs:
- 5000:5000 - 5000:5000
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c uses: actions/checkout@v3
- name: Restore Go cache - name: Restore Go cache
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 uses: actions/cache@v3
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
restore-keys: | restore-keys: |
${{ runner.os }}-go1.18- ${{ runner.os }}-go1.18-
- name: Setup Go - name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 uses: actions/setup-go@v3
with: with:
go-version: 1.19.x go-version: 1.19.x
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@d8ccf8fb623ce1bb360ae2f45f323d9d5c5e9f00 # v1.5.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
node_image: kindest/node:v1.23.13
- name: Setup Calico for network policy - name: Setup Calico for network policy
run: | run: |
kubectl apply -f https://docs.projectcalico.org/v3.20/manifests/calico.yaml kubectl apply -f https://docs.projectcalico.org/v3.20/manifests/calico.yaml
@@ -86,6 +81,13 @@ jobs:
--tag-semver=">=3.2.3" \ --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
@@ -176,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
@@ -216,13 +218,14 @@ jobs:
/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: |

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,43 +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@ac593985615ec2ede58e132d2e21d2b1cbd6127c 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@6edd4406fa81c3da01a34fa6f6343087c207a568 uses: actions/setup-go@v3
with: with:
go-version: 1.19.x go-version: 1.19.x
- name: Setup QEMU - name: Setup QEMU
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2 uses: docker/setup-qemu-action@v2
- name: Setup Docker Buildx - name: Setup Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # v2 uses: docker/setup-buildx-action@v2
- name: Setup Syft - name: Setup Syft
uses: anchore/sbom-action/download-syft@07978da4bdb4faa726e52dfc6b1bed63d4b56479 # v0.13.3 uses: anchore/sbom-action/download-syft@v0
- name: Setup Cosign - name: Setup Cosign
uses: sigstore/cosign-installer@9becc617647dfa20ae7b1151972e9b3a2c338a2b # v2.8.1 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 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 uses: docker/login-action@v2
with: with:
username: fluxcdbot username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }} password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@@ -53,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
@@ -73,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 # v3 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
@@ -81,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@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- 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
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # 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 }}@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@9becc617647dfa20ae7b1151972e9b3a2c338a2b # v2.8.1
- 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

@@ -1,7 +1,6 @@
name: scan name: scan
on: on:
workflow_dispatch:
push: push:
branches: [ main ] branches: [ main ]
pull_request: pull_request:
@@ -10,67 +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@ac593985615ec2ede58e132d2e21d2b1cbd6127c - 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 # v1 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@ac593985615ec2ede58e132d2e21d2b1cbd6127c - 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@6edd4406fa81c3da01a34fa6f6343087c207a568
with:
go-version: 1.19.x
- name: Download modules and build manifests
run: | run: |
make tidy
make cmd/flux/.manifests.done make cmd/flux/.manifests.done
- uses: snyk/actions/setup@806182742461562b67788a64410098c9d9b96adb - 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@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2 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@ac593985615ec2ede58e132d2e21d2b1cbd6127c uses: actions/checkout@v3
- name: Set up Go - name: Set up Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 uses: actions/setup-go@v2
with: with:
go-version: 1.19.x go-version: 1.19.x
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2 uses: github/codeql-action/init@v2
with: with:
languages: go languages: go
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2 uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2 uses: github/codeql-action/analyze@v2

View File

@@ -1,4 +1,4 @@
name: update name: Update Components
on: on:
workflow_dispatch: workflow_dispatch:
@@ -7,26 +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@ac593985615ec2ede58e132d2e21d2b1cbd6127c uses: actions/checkout@v3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 uses: actions/setup-go@v3
with: with:
go-version: 1.19.x go-version: 1.19.x
- 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')
@@ -53,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
} }
@@ -71,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@2b011faafdcbc9ceb11414d64d0573f37c774b04 # v4 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

@@ -1,15 +1,19 @@
FROM alpine:3.17 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.26.1 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.17 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

@@ -1,13 +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)
[![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) [![e2e](https://github.com/fluxcd/flux2/workflows/e2e/badge.svg)](https://github.com/fluxcd/flux2/actions)
[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/flux2)](https://artifacthub.io/packages/helm/fluxcd-community/flux2) [![report](https://goreportcard.com/badge/github.com/fluxcd/flux2)](https://goreportcard.com/report/github.com/fluxcd/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
@@ -19,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
@@ -77,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

@@ -31,13 +31,13 @@ import (
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,14 +22,14 @@ 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/internal/bootstrap"
"github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/internal/bootstrap/provider"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/bootstrap"
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
"github.com/fluxcd/flux2/pkg/manifestgen" "github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
@@ -38,26 +38,29 @@ import (
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/internal/bootstrap"
"github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/bootstrap"
"github.com/fluxcd/flux2/pkg/manifestgen" "github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync" "github.com/fluxcd/flux2/pkg/manifestgen/sync"
"github.com/fluxcd/pkg/git"
"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 a 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,14 +22,14 @@ 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/internal/bootstrap"
"github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/internal/bootstrap/provider"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/bootstrap"
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
"github.com/fluxcd/flux2/pkg/manifestgen" "github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
@@ -38,35 +38,38 @@ import (
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,14 +24,14 @@ 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/internal/bootstrap"
"github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/internal/bootstrap/provider"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/bootstrap"
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
"github.com/fluxcd/flux2/pkg/manifestgen" "github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
@@ -40,11 +40,11 @@ import (
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>
@@ -136,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()
@@ -180,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{
@@ -224,18 +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 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 = 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
} }
@@ -250,26 +242,23 @@ 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))

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,13 +30,10 @@ import (
var buildArtifactCmd = &cobra.Command{ var buildArtifactCmd = &cobra.Command{
Use: "artifact", Use: "artifact",
Short: "Build artifact", Short: "Build artifact",
Long: `The build artifact command creates a tgz file with the manifests from the given directory or a single manifest file.`, Long: `The build artifact command creates a tgz file with the manifests from the given directory.`,
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
`, `,
@@ -57,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")
@@ -69,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() ociClient := oci.NewLocalClient()
if err := ociClient.Build(buildArtifactArgs.output, path, buildArtifactArgs.ignorePaths); err != nil { if err := ociClient.Build(buildArtifactArgs.output, buildArtifactArgs.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

@@ -40,11 +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`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: buildKsCmdRun, RunE: buildKsCmdRun,
} }
@@ -52,7 +48,6 @@ flux build kustomization my-app --path ./path/to/local/manifests --kustomization
type buildKsFlags struct { type buildKsFlags struct {
kustomizationFile string kustomizationFile string
path string path string
dryRun bool
} }
var buildKsArgs buildKsFlags var buildKsArgs buildKsFlags
@@ -60,11 +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().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)
} }
@@ -78,32 +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),
)
} else {
builder, err = build.NewBuilder(name, buildKsArgs.path,
build.WithClientConfig(kubeconfigArgs, kubeclientOptions),
build.WithTimeout(rootArgs.timeout),
build.WithKustomizationFile(buildKsArgs.kustomizationFile),
)
}
if err != nil { if err != nil {
return err return err
} }

View File

@@ -142,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{
@@ -171,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

@@ -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

@@ -34,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

@@ -28,7 +28,7 @@ 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/internal/utils" "github.com/fluxcd/flux2/internal/utils"

View File

@@ -28,7 +28,7 @@ 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/internal/utils" "github.com/fluxcd/flux2/internal/utils"

View File

@@ -28,7 +28,7 @@ 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{

View File

@@ -26,7 +26,7 @@ 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{

View File

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

View File

@@ -28,7 +28,7 @@ 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/internal/utils" "github.com/fluxcd/flux2/internal/utils"

View File

@@ -21,7 +21,6 @@ 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"
@@ -136,12 +135,8 @@ 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
@@ -152,13 +147,7 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
} }
opts.Username = secretGitArgs.username opts.Username = secretGitArgs.username
opts.Password = secretGitArgs.password opts.Password = secretGitArgs.password
if secretGitArgs.caFile != "" { opts.CAFilePath = 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

@@ -18,8 +18,6 @@ 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"
@@ -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

@@ -18,8 +18,6 @@ package main
import ( import (
"context" "context"
"fmt"
"os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@@ -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

@@ -55,6 +55,7 @@ type sourceGitFlags struct {
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
@@ -135,6 +136,7 @@ func init() {
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,
@@ -176,6 +178,10 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
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
@@ -214,6 +220,10 @@ 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.gitImplementation != "" {
gitRepository.Spec.GitImplementation = sourceGitArgs.gitImplementation.String()
}
if sourceGitArgs.semver != "" { if sourceGitArgs.semver != "" {
gitRepository.Spec.Reference.SemVer = sourceGitArgs.semver gitRepository.Spec.Reference.SemVer = sourceGitArgs.semver
} else if sourceGitArgs.tag != "" { } else if sourceGitArgs.tag != "" {
@@ -249,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

@@ -168,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)
@@ -195,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

@@ -19,7 +19,7 @@ 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{

View File

@@ -19,7 +19,7 @@ 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{

View File

@@ -19,7 +19,7 @@ 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{

View File

@@ -19,7 +19,7 @@ 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{

View File

@@ -19,7 +19,7 @@ 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 deleteReceiverCmd = &cobra.Command{ var deleteReceiverCmd = &cobra.Command{

View File

@@ -1,111 +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"
"github.com/fluxcd/flux2/internal/flags"
oci "github.com/fluxcd/pkg/oci/client"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/spf13/cobra"
)
var diffArtifactCmd = &cobra.Command{
Use: "artifact",
Short: "Diff Artifact",
Long: `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.NewLocalClient()
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

@@ -77,21 +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())
} 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))
} }
if err != nil { if err != nil {

View File

@@ -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

@@ -20,7 +20,7 @@ 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{

View File

@@ -20,7 +20,7 @@ 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{

View File

@@ -20,7 +20,7 @@ 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{

View File

@@ -20,7 +20,7 @@ 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 exportImageRepositoryCmd = &cobra.Command{ var exportImageRepositoryCmd = &cobra.Command{

View File

@@ -20,7 +20,7 @@ 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 exportReceiverCmd = &cobra.Command{ var exportReceiverCmd = &cobra.Command{

View File

@@ -8,92 +8,78 @@ import (
) )
func TestExport(t *testing.T) { func TestExport(t *testing.T) {
namespace := allocateNamespace("flux-system")
objectFile := "testdata/export/objects.yaml"
tmpl := map[string]string{
"fluxns": namespace,
}
testEnv.CreateObjectFile(objectFile, tmpl, t)
cases := []struct { cases := []struct {
name string name string
arg string arg string
goldenFile string goldenFile string
tmpl map[string]string
}{ }{
{ {
"alert-provider", "alert-provider",
"export alert-provider slack", "export alert-provider slack",
"testdata/export/provider.yaml", "testdata/export/provider.yaml",
tmpl,
}, },
{ {
"alert", "alert",
"export alert flux-system", "export alert flux-system",
"testdata/export/alert.yaml", "testdata/export/alert.yaml",
tmpl,
}, },
{ {
"image policy", "image policy",
"export image policy flux-system", "export image policy flux-system",
"testdata/export/image-policy.yaml", "testdata/export/image-policy.yaml",
tmpl,
}, },
{ {
"image repository", "image repository",
"export image repository flux-system", "export image repository flux-system",
"testdata/export/image-repo.yaml", "testdata/export/image-repo.yaml",
tmpl,
}, },
{ {
"image update", "image update",
"export image update flux-system", "export image update flux-system",
"testdata/export/image-update.yaml", "testdata/export/image-update.yaml",
tmpl,
}, },
{ {
"source git", "source git",
"export source git flux-system", "export source git flux-system",
"testdata/export/git-repo.yaml", "testdata/export/git-repo.yaml",
tmpl,
}, },
{ {
"source helm", "source helm",
"export source helm flux-system", "export source helm flux-system",
"testdata/export/helm-repo.yaml", "testdata/export/helm-repo.yaml",
tmpl,
}, },
{ {
"receiver", "receiver",
"export receiver flux-system", "export receiver flux-system",
"testdata/export/receiver.yaml", "testdata/export/receiver.yaml",
tmpl,
}, },
{ {
"kustomization", "kustomization",
"export kustomization flux-system", "export kustomization flux-system",
"testdata/export/ks.yaml", "testdata/export/ks.yaml",
tmpl,
}, },
{ {
"helmrelease", "helmrelease",
"export helmrelease flux-system", "export helmrelease flux-system",
"testdata/export/helm-release.yaml", "testdata/export/helm-release.yaml",
tmpl,
}, },
{ {
"bucket", "bucket",
"export source bucket flux-system", "export source bucket flux-system",
"testdata/export/bucket.yaml", "testdata/export/bucket.yaml",
tmpl,
}, },
} }
objectFile := "testdata/export/objects.yaml"
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
testEnv.CreateObjectFile(objectFile, tmpl, t)
for _, tt := range cases { for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{ cmd := cmdTestCase{
args: tt.arg + " -n=" + namespace, args: tt.arg + " -n=" + tmpl["fluxns"],
assert: assertGoldenTemplateFile(tt.goldenFile, tmpl), assert: assertGoldenTemplateFile(tt.goldenFile, tmpl),
} }

View File

@@ -163,16 +163,9 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
if get.list.len() == 0 { if get.list.len() == 0 {
if len(args) > 0 { if len(args) > 0 {
logger.Failuref("%s object '%s' not found in %s namespace", logger.Failuref("%s object '%s' not found in '%s' namespace", get.kind, args[0], *kubeconfigArgs.Namespace)
get.kind,
args[0],
namespaceNameOrAny(getArgs.allNamespaces, *kubeconfigArgs.Namespace),
)
} else if !getAll { } else if !getAll {
logger.Failuref("no %s objects found in %s namespace", logger.Failuref("no %s objects found in %s namespace", get.kind, *kubeconfigArgs.Namespace)
get.kind,
namespaceNameOrAny(getArgs.allNamespaces, *kubeconfigArgs.Namespace),
)
} }
return nil return nil
} }
@@ -199,13 +192,6 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func namespaceNameOrAny(allNamespaces bool, namespaceName string) string {
if allNamespaces {
return "any"
}
return fmt.Sprintf("%q", namespaceName)
}
func getRowsToPrint(getAll bool, list summarisable) ([][]string, error) { func getRowsToPrint(getAll bool, list summarisable) ([][]string, error) {
noFilter := true noFilter := true
var conditionType, conditionStatus string var conditionType, conditionStatus string

View File

@@ -24,7 +24,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var getAlertCmd = &cobra.Command{ var getAlertCmd = &cobra.Command{

View File

@@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var getAlertProviderCmd = &cobra.Command{ var getAlertProviderCmd = &cobra.Command{

View File

@@ -23,7 +23,7 @@ import (
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var getAllCmd = &cobra.Command{ var getAllCmd = &cobra.Command{

View File

@@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
) )
var getImageAllCmd = &cobra.Command{ var getImageAllCmd = &cobra.Command{

View File

@@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
) )
var getImagePolicyCmd = &cobra.Command{ var getImagePolicyCmd = &cobra.Command{

View File

@@ -25,7 +25,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
) )
var getImageRepositoryCmd = &cobra.Command{ var getImageRepositoryCmd = &cobra.Command{

View File

@@ -18,15 +18,15 @@ package main
import ( import (
"fmt" "fmt"
"regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/flux2/internal/utils"
) )
var getKsCmd = &cobra.Command{ var getKsCmd = &cobra.Command{
@@ -80,8 +80,10 @@ func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool, in
item := a.Items[i] item := a.Items[i]
revision := item.Status.LastAppliedRevision revision := item.Status.LastAppliedRevision
status, msg := statusAndMessage(item.Status.Conditions) status, msg := statusAndMessage(item.Status.Conditions)
revision = utils.TruncateHex(revision) if status == string(metav1.ConditionTrue) {
msg = utils.TruncateHex(msg) revision = shortenCommitSha(revision)
msg = shortenCommitSha(msg)
}
return append(nameColumns(&item, includeNamespace, includeKind), return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
} }
@@ -98,3 +100,13 @@ func (a kustomizationListAdapter) statusSelectorMatches(i int, conditionType, co
item := a.Items[i] item := a.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions) return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
} }
func shortenCommitSha(msg string) string {
r := regexp.MustCompile("/([a-f0-9]{40})$")
sha := r.FindString(msg)
if sha != "" {
msg = strings.Replace(msg, sha, string([]rune(sha)[:8]), -1)
}
return msg
}

View File

@@ -24,7 +24,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var getReceiverCmd = &cobra.Command{ var getReceiverCmd = &cobra.Command{

View File

@@ -25,8 +25,6 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/internal/utils"
) )
var getSourceBucketCmd = &cobra.Command{ var getSourceBucketCmd = &cobra.Command{
@@ -82,8 +80,6 @@ func (a *bucketListAdapter) summariseItem(i int, includeNamespace bool, includeK
revision = item.GetArtifact().Revision revision = item.GetArtifact().Revision
} }
status, msg := statusAndMessage(item.Status.Conditions) status, msg := statusAndMessage(item.Status.Conditions)
revision = utils.TruncateHex(revision)
msg = utils.TruncateHex(msg)
return append(nameColumns(&item, includeNamespace, includeKind), return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
} }

View File

@@ -25,8 +25,6 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/internal/utils"
) )
var getSourceHelmChartCmd = &cobra.Command{ var getSourceHelmChartCmd = &cobra.Command{
@@ -82,9 +80,6 @@ func (a *helmChartListAdapter) summariseItem(i int, includeNamespace bool, inclu
revision = item.GetArtifact().Revision revision = item.GetArtifact().Revision
} }
status, msg := statusAndMessage(item.Status.Conditions) status, msg := statusAndMessage(item.Status.Conditions)
// NB: do not shorten revision as it contains a SemVer
// Message may still contain reference of e.g. commit chart was build from
msg = utils.TruncateHex(msg)
return append(nameColumns(&item, includeNamespace, includeKind), return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
} }

View File

@@ -22,11 +22,10 @@ import (
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/internal/utils"
) )
var getSourceGitCmd = &cobra.Command{ var getSourceGitCmd = &cobra.Command{
@@ -82,8 +81,10 @@ func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i
revision = item.GetArtifact().Revision revision = item.GetArtifact().Revision
} }
status, msg := statusAndMessage(item.Status.Conditions) status, msg := statusAndMessage(item.Status.Conditions)
revision = utils.TruncateHex(revision) if status == string(metav1.ConditionTrue) {
msg = utils.TruncateHex(msg) revision = shortenCommitSha(revision)
msg = shortenCommitSha(msg)
}
return append(nameColumns(&item, includeNamespace, includeKind), return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
} }

View File

@@ -25,8 +25,6 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/internal/utils"
) )
var getSourceHelmCmd = &cobra.Command{ var getSourceHelmCmd = &cobra.Command{
@@ -82,8 +80,6 @@ func (a *helmRepositoryListAdapter) summariseItem(i int, includeNamespace bool,
revision = item.GetArtifact().Revision revision = item.GetArtifact().Revision
} }
status, msg := statusAndMessage(item.Status.Conditions) status, msg := statusAndMessage(item.Status.Conditions)
revision = utils.TruncateHex(revision)
msg = utils.TruncateHex(msg)
return append(nameColumns(&item, includeNamespace, includeKind), return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
} }

View File

@@ -25,8 +25,6 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/internal/utils"
) )
var getSourceOCIRepositoryCmd = &cobra.Command{ var getSourceOCIRepositoryCmd = &cobra.Command{
@@ -82,8 +80,6 @@ func (a *ociRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i
revision = item.GetArtifact().Revision revision = item.GetArtifact().Revision
} }
status, msg := statusAndMessage(item.Status.Conditions) status, msg := statusAndMessage(item.Status.Conditions)
revision = utils.TruncateHex(revision)
msg = utils.TruncateHex(msg)
return append(nameColumns(&item, includeNamespace, includeKind), return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
} }

View File

@@ -22,61 +22,51 @@ package main
import "testing" import "testing"
func TestHelmReleaseFromGit(t *testing.T) { func TestHelmReleaseFromGit(t *testing.T) {
namespace := allocateNamespace("thrfg")
del, err := execSetupTestNamespace(namespace)
if err != nil {
t.Fatal(err)
}
t.Cleanup(del)
tmpl := map[string]string{"ns": namespace}
cases := []struct { cases := []struct {
args string args string
goldenFile string goldenFile string
tmpl map[string]string
}{ }{
{ {
"create source git thrfg --url=https://github.com/stefanprodan/podinfo --branch=main --tag=6.0.0", "create source git thrfg --url=https://github.com/stefanprodan/podinfo --branch=main --tag=6.0.0",
"testdata/helmrelease/create_source_git.golden", "testdata/helmrelease/create_source_git.golden",
nil,
}, },
{ {
"create helmrelease thrfg --source=GitRepository/thrfg --chart=./charts/podinfo", "create helmrelease thrfg --source=GitRepository/thrfg --chart=./charts/podinfo",
"testdata/helmrelease/create_helmrelease_from_git.golden", "testdata/helmrelease/create_helmrelease_from_git.golden",
nil,
}, },
{ {
"get helmrelease thrfg", "get helmrelease thrfg",
"testdata/helmrelease/get_helmrelease_from_git.golden", "testdata/helmrelease/get_helmrelease_from_git.golden",
nil,
}, },
{ {
"reconcile helmrelease thrfg --with-source", "reconcile helmrelease thrfg --with-source",
"testdata/helmrelease/reconcile_helmrelease_from_git.golden", "testdata/helmrelease/reconcile_helmrelease_from_git.golden",
tmpl,
}, },
{ {
"suspend helmrelease thrfg", "suspend helmrelease thrfg",
"testdata/helmrelease/suspend_helmrelease_from_git.golden", "testdata/helmrelease/suspend_helmrelease_from_git.golden",
tmpl,
}, },
{ {
"resume helmrelease thrfg", "resume helmrelease thrfg",
"testdata/helmrelease/resume_helmrelease_from_git.golden", "testdata/helmrelease/resume_helmrelease_from_git.golden",
tmpl,
}, },
{ {
"delete helmrelease thrfg --silent", "delete helmrelease thrfg --silent",
"testdata/helmrelease/delete_helmrelease_from_git.golden", "testdata/helmrelease/delete_helmrelease_from_git.golden",
tmpl,
}, },
} }
namespace := allocateNamespace("thrfg")
del, err := setupTestNamespace(namespace)
if err != nil {
t.Fatal(err)
}
defer del()
for _, tc := range cases { for _, tc := range cases {
cmd := cmdTestCase{ cmd := cmdTestCase{
args: tc.args + " -n=" + namespace, args: tc.args + " -n=" + namespace,
assert: assertGoldenTemplateFile(tc.goldenFile, tc.tmpl), assert: assertGoldenTemplateFile(tc.goldenFile, map[string]string{"ns": namespace}),
} }
cmd.runTestCmd(t) cmd.runTestCmd(t)
} }

View File

@@ -20,7 +20,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
) )
// These are general-purpose adapters for attaching methods to, for // These are general-purpose adapters for attaching methods to, for

View File

@@ -22,13 +22,6 @@ package main
import "testing" import "testing"
func TestImageScanning(t *testing.T) { func TestImageScanning(t *testing.T) {
namespace := allocateNamespace("tis")
del, err := execSetupTestNamespace(namespace)
if err != nil {
t.Fatal(err)
}
t.Cleanup(del)
cases := []struct { cases := []struct {
args string args string
goldenFile string goldenFile string
@@ -55,6 +48,13 @@ func TestImageScanning(t *testing.T) {
}, },
} }
namespace := allocateNamespace("tis")
del, err := setupTestNamespace(namespace)
if err != nil {
t.Fatal(err)
}
defer del()
for _, tc := range cases { for _, tc := range cases {
cmd := cmdTestCase{ cmd := cmdTestCase{
args: tc.args + " -n=" + namespace, args: tc.args + " -n=" + namespace,

View File

@@ -59,6 +59,7 @@ If a previous version is installed, then an in-place upgrade will be performed.`
type installFlags struct { type installFlags struct {
export bool export bool
dryRun bool
version string version string
defaultComponents []string defaultComponents []string
extraComponents []string extraComponents []string
@@ -68,6 +69,7 @@ type installFlags struct {
watchAllNamespaces bool watchAllNamespaces bool
networkPolicy bool networkPolicy bool
manifestsPath string manifestsPath string
arch flags.Arch
logLevel flags.LogLevel logLevel flags.LogLevel
tokenAuth bool tokenAuth bool
clusterDomain string clusterDomain string
@@ -79,6 +81,8 @@ var installArgs = NewInstallFlags()
func init() { func init() {
installCmd.Flags().BoolVar(&installArgs.export, "export", false, installCmd.Flags().BoolVar(&installArgs.export, "export", false,
"write the install manifests to stdout and exit") "write the install manifests to stdout and exit")
installCmd.Flags().BoolVarP(&installArgs.dryRun, "dry-run", "", false,
"only print the object that would be applied")
installCmd.Flags().StringVarP(&installArgs.version, "version", "v", "", installCmd.Flags().StringVarP(&installArgs.version, "version", "v", "",
"toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases") "toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases")
installCmd.Flags().StringSliceVar(&installArgs.defaultComponents, "components", rootArgs.defaults.Components, installCmd.Flags().StringSliceVar(&installArgs.defaultComponents, "components", rootArgs.defaults.Components,
@@ -90,6 +94,7 @@ func init() {
"container registry where the toolkit images are published") "container registry where the toolkit images are published")
installCmd.Flags().StringVar(&installArgs.imagePullSecret, "image-pull-secret", "", installCmd.Flags().StringVar(&installArgs.imagePullSecret, "image-pull-secret", "",
"Kubernetes secret name used for pulling the toolkit images from a private registry") "Kubernetes secret name used for pulling the toolkit images from a private registry")
installCmd.Flags().Var(&installArgs.arch, "arch", installArgs.arch.Description())
installCmd.Flags().BoolVar(&installArgs.watchAllNamespaces, "watch-all-namespaces", rootArgs.defaults.WatchAllNamespaces, installCmd.Flags().BoolVar(&installArgs.watchAllNamespaces, "watch-all-namespaces", rootArgs.defaults.WatchAllNamespaces,
"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed") "watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed")
installCmd.Flags().Var(&installArgs.logLevel, "log-level", installArgs.logLevel.Description()) installCmd.Flags().Var(&installArgs.logLevel, "log-level", installArgs.logLevel.Description())
@@ -99,7 +104,8 @@ func init() {
installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil, installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil,
"list of toleration keys used to schedule the components pods onto nodes with matching taints") "list of toleration keys used to schedule the components pods onto nodes with matching taints")
installCmd.Flags().MarkHidden("manifests") installCmd.Flags().MarkHidden("manifests")
installCmd.Flags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64")
installCmd.Flags().MarkDeprecated("dry-run", "use 'flux install --export | kubectl apply --dry-run=client -f-'")
rootCmd.AddCommand(installCmd) rootCmd.AddCommand(installCmd)
} }
@@ -183,6 +189,11 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("manifests build completed") logger.Successf("manifests build completed")
logger.Actionf("installing components in %s namespace", *kubeconfigArgs.Namespace) logger.Actionf("installing components in %s namespace", *kubeconfigArgs.Namespace)
if installArgs.dryRun {
logger.Successf("install dry-run finished")
return nil
}
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path)) applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))
if err != nil { if err != nil {
return fmt.Errorf("install failed: %w", err) return fmt.Errorf("install failed: %w", err)

View File

@@ -25,7 +25,9 @@ func TestInstall(t *testing.T) {
// Given that this test uses an invalid namespace, it ensures // Given that this test uses an invalid namespace, it ensures
// to restore whatever value it had previously. // to restore whatever value it had previously.
currentNamespace := *kubeconfigArgs.Namespace currentNamespace := *kubeconfigArgs.Namespace
t.Cleanup(func() { *kubeconfigArgs.Namespace = currentNamespace }) defer func() {
*kubeconfigArgs.Namespace = currentNamespace
}()
tests := []struct { tests := []struct {
name string name string

View File

@@ -22,61 +22,51 @@ package main
import "testing" import "testing"
func TestKustomizationFromGit(t *testing.T) { func TestKustomizationFromGit(t *testing.T) {
namespace := allocateNamespace("tkfg")
del, err := execSetupTestNamespace(namespace)
if err != nil {
t.Fatal(err)
}
t.Cleanup(del)
tmpl := map[string]string{"ns": namespace}
cases := []struct { cases := []struct {
args string args string
goldenFile string goldenFile string
tmpl map[string]string
}{ }{
{ {
"create source git tkfg --url=https://github.com/stefanprodan/podinfo --branch=main --tag=6.0.0", "create source git tkfg --url=https://github.com/stefanprodan/podinfo --branch=main --tag=6.0.0",
"testdata/kustomization/create_source_git.golden", "testdata/kustomization/create_source_git.golden",
nil,
}, },
{ {
"create kustomization tkfg --source=tkfg --path=./deploy/overlays/dev --prune=true --interval=5m --health-check=Deployment/frontend.dev --health-check=Deployment/backend.dev --health-check-timeout=3m", "create kustomization tkfg --source=tkfg --path=./deploy/overlays/dev --prune=true --interval=5m --health-check=Deployment/frontend.dev --health-check=Deployment/backend.dev --health-check-timeout=3m",
"testdata/kustomization/create_kustomization_from_git.golden", "testdata/kustomization/create_kustomization_from_git.golden",
nil,
}, },
{ {
"get kustomization tkfg", "get kustomization tkfg",
"testdata/kustomization/get_kustomization_from_git.golden", "testdata/kustomization/get_kustomization_from_git.golden",
nil,
}, },
{ {
"reconcile kustomization tkfg --with-source", "reconcile kustomization tkfg --with-source",
"testdata/kustomization/reconcile_kustomization_from_git.golden", "testdata/kustomization/reconcile_kustomization_from_git.golden",
tmpl,
}, },
{ {
"suspend kustomization tkfg", "suspend kustomization tkfg",
"testdata/kustomization/suspend_kustomization_from_git.golden", "testdata/kustomization/suspend_kustomization_from_git.golden",
tmpl,
}, },
{ {
"resume kustomization tkfg", "resume kustomization tkfg",
"testdata/kustomization/resume_kustomization_from_git.golden", "testdata/kustomization/resume_kustomization_from_git.golden",
tmpl,
}, },
{ {
"delete kustomization tkfg --silent", "delete kustomization tkfg --silent",
"testdata/kustomization/delete_kustomization_from_git.golden", "testdata/kustomization/delete_kustomization_from_git.golden",
tmpl,
}, },
} }
namespace := allocateNamespace("tkfg")
del, err := setupTestNamespace(namespace)
if err != nil {
t.Fatal(err)
}
defer del()
for _, tc := range cases { for _, tc := range cases {
cmd := cmdTestCase{ cmd := cmdTestCase{
args: tc.args + " -n=" + namespace, args: tc.args + " -n=" + namespace,
assert: assertGoldenTemplateFile(tc.goldenFile, tc.tmpl), assert: assertGoldenTemplateFile(tc.goldenFile, map[string]string{"ns": namespace}),
} }
cmd.runTestCmd(t) cmd.runTestCmd(t)
} }

View File

@@ -65,7 +65,7 @@ func TestMain(m *testing.M) {
os.Exit(code) os.Exit(code)
} }
func execSetupTestNamespace(namespace string) (func(), error) { func setupTestNamespace(namespace string) (func(), error) {
kubectlArgs := []string{"create", "namespace", namespace} kubectlArgs := []string{"create", "namespace", namespace}
_, err := utils.ExecKubectlCommand(context.TODO(), utils.ModeStderrOS, *kubeconfigArgs.KubeConfig, *kubeconfigArgs.Context, kubectlArgs...) _, err := utils.ExecKubectlCommand(context.TODO(), utils.ModeStderrOS, *kubeconfigArgs.KubeConfig, *kubeconfigArgs.Context, kubectlArgs...)
if err != nil { if err != nil {

View File

@@ -18,15 +18,12 @@ package main
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"os" "os"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
reg "github.com/google/go-containerregistry/pkg/name"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sigs.k8s.io/yaml"
oci "github.com/fluxcd/pkg/oci/client" oci "github.com/fluxcd/pkg/oci/client"
) )
@@ -34,43 +31,21 @@ import (
var pushArtifactCmd = &cobra.Command{ var pushArtifactCmd = &cobra.Command{
Use: "artifact", Use: "artifact",
Short: "Push artifact", Short: "Push artifact",
Long: `The push artifact command creates a tarball from the given directory or the single file and uploads the artifact to an OCI repository. Long: `The push artifact command creates a tarball from the given directory and uploads the artifact to an OCI repository.
The command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`, The command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`,
Example: ` # Push manifests to GHCR using the short Git SHA as the OCI artifact tag Example: ` # Push manifests to GHCR using the short Git SHA as the OCI artifact tag
echo $GITHUB_PAT | docker login ghcr.io --username flux --password-stdin echo $GITHUB_PAT | docker login ghcr.io --username flux --password-stdin
flux push artifact oci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) \ flux push artifact oci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) \
--path="./path/to/local/manifests" \ --path="./path/to/local/manifests" \
--source="$(git config --get remote.origin.url)" \ --source="$(git config --get remote.origin.url)" \
--revision="$(git branch --show-current)@sha1:$(git rev-parse HEAD)" --revision="$(git branch --show-current)/$(git rev-parse HEAD)"
# Push and sign artifact with cosign
digest_url = $(flux push artifact \
oci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) \
--source="$(git config --get remote.origin.url)" \
--revision="$(git branch --show-current)@sha1:$(git rev-parse HEAD)" \
--path="./path/to/local/manifest.yaml" \
--output json | \
jq -r '. | .repository + "@" + .digest')
cosign sign $digest_url
# Push manifests passed into stdin to GHCR
kustomize build . | flux push artifact oci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) -p - \
--source="$(git config --get remote.origin.url)" \
--revision="$(git branch --show-current)@sha1:$(git rev-parse HEAD)"
# Push single manifest file to GHCR using the short Git SHA as the OCI artifact tag
echo $GITHUB_PAT | docker login ghcr.io --username flux --password-stdin
flux push artifact oci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) \
--path="./path/to/local/manifest.yaml" \
--source="$(git config --get remote.origin.url)" \
--revision="$(git branch --show-current)@sha1:$(git rev-parse HEAD)"
# Push manifests to Docker Hub using the Git tag as the OCI artifact tag # Push manifests to Docker Hub using the Git tag as the OCI artifact tag
echo $DOCKER_PAT | docker login --username flux --password-stdin echo $DOCKER_PAT | docker login --username flux --password-stdin
flux push artifact oci://docker.io/org/app-config:$(git tag --points-at HEAD) \ flux push artifact oci://docker.io/org/app-config:$(git tag --points-at HEAD) \
--path="./path/to/local/manifests" \ --path="./path/to/local/manifests" \
--source="$(git config --get remote.origin.url)" \ --source="$(git config --get remote.origin.url)" \
--revision="$(git tag --points-at HEAD)@sha1:$(git rev-parse HEAD)" --revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)"
# Login directly to the registry provider # Login directly to the registry provider
# You might need to export the following variable if you use local config files for AWS: # You might need to export the following variable if you use local config files for AWS:
@@ -78,14 +53,14 @@ The command can read the credentials from '~/.docker/config.json' but they can a
flux push artifact oci://<account>.dkr.ecr.<region>.amazonaws.com/foo:v1:$(git tag --points-at HEAD) \ flux push artifact oci://<account>.dkr.ecr.<region>.amazonaws.com/foo:v1:$(git tag --points-at HEAD) \
--path="./path/to/local/manifests" \ --path="./path/to/local/manifests" \
--source="$(git config --get remote.origin.url)" \ --source="$(git config --get remote.origin.url)" \
--revision="$(git tag --points-at HEAD)@sha1:$(git rev-parse HEAD)" \ --revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)" \
--provider aws --provider aws
# Or pass credentials directly # Or pass credentials directly
flux push artifact oci://docker.io/org/app-config:$(git tag --points-at HEAD) \ flux push artifact oci://docker.io/org/app-config:$(git tag --points-at HEAD) \
--path="./path/to/local/manifests" \ --path="./path/to/local/manifests" \
--source="$(git config --get remote.origin.url)" \ --source="$(git config --get remote.origin.url)" \
--revision="$(git tag --points-at HEAD)@sha1:$(git rev-parse HEAD)" \ --revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)" \
--creds flux:$DOCKER_PAT --creds flux:$DOCKER_PAT
`, `,
RunE: pushArtifactCmdRun, RunE: pushArtifactCmdRun,
@@ -98,7 +73,6 @@ type pushArtifactFlags struct {
creds string creds string
provider flags.SourceOCIProvider provider flags.SourceOCIProvider
ignorePaths []string ignorePaths []string
output string
} }
var pushArtifactArgs = newPushArtifactFlags() var pushArtifactArgs = newPushArtifactFlags()
@@ -112,12 +86,10 @@ func newPushArtifactFlags() pushArtifactFlags {
func init() { func init() {
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.path, "path", "", "path to the directory where the Kubernetes manifests are located") pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.path, "path", "", "path to the directory where the Kubernetes manifests are located")
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.source, "source", "", "the source address, e.g. the Git URL") pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.source, "source", "", "the source address, e.g. the Git URL")
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.revision, "revision", "", "the source revision in the format '<branch|tag>@sha1:<commit-sha>'") pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.revision, "revision", "", "the source revision in the format '<branch|tag>/<commit-sha>'")
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic") pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
pushArtifactCmd.Flags().Var(&pushArtifactArgs.provider, "provider", pushArtifactArgs.provider.Description()) pushArtifactCmd.Flags().Var(&pushArtifactArgs.provider, "provider", pushArtifactArgs.provider.Description())
pushArtifactCmd.Flags().StringSliceVar(&pushArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format") pushArtifactCmd.Flags().StringSliceVar(&pushArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
pushArtifactCmd.Flags().StringVarP(&pushArtifactArgs.output, "output", "o", "",
"the format in which the artifact digest should be printed, can be 'json' or 'yaml'")
pushCmd.AddCommand(pushArtifactCmd) pushCmd.AddCommand(pushArtifactCmd)
} }
@@ -145,18 +117,8 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
path := pushArtifactArgs.path if fs, err := os.Stat(pushArtifactArgs.path); err != nil || !fs.IsDir() {
if pushArtifactArgs.path == "-" { return fmt.Errorf("invalid path %q", pushArtifactArgs.path)
path, err = saveReaderToFile(os.Stdin)
if err != nil {
return err
}
defer os.Remove(path)
}
if _, err := os.Stat(path); err != nil {
return fmt.Errorf("invalid path '%s', must point to an existing directory or file: %w", path, err)
} }
meta := oci.Metadata{ meta := oci.Metadata{
@@ -188,54 +150,14 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
if pushArtifactArgs.output == "" { logger.Actionf("pushing artifact to %s", url)
logger.Actionf("pushing artifact to %s", url)
}
digestURL, err := ociClient.Push(ctx, url, path, meta, pushArtifactArgs.ignorePaths) digest, err := ociClient.Push(ctx, url, pushArtifactArgs.path, meta, pushArtifactArgs.ignorePaths)
if err != nil { if err != nil {
return fmt.Errorf("pushing artifact failed: %w", err) return fmt.Errorf("pushing artifact failed: %w", err)
} }
digest, err := reg.NewDigest(digestURL) logger.Successf("artifact successfully pushed to %s", digest)
if err != nil {
return fmt.Errorf("artifact digest parsing failed: %w", err)
}
tag, err := reg.NewTag(url)
if err != nil {
return fmt.Errorf("artifact tag parsing failed: %w", err)
}
info := struct {
URL string `json:"url"`
Repository string `json:"repository"`
Tag string `json:"tag"`
Digest string `json:"digest"`
}{
URL: fmt.Sprintf("oci://%s", digestURL),
Repository: digest.Repository.Name(),
Tag: tag.TagStr(),
Digest: digest.DigestStr(),
}
switch pushArtifactArgs.output {
case "json":
marshalled, err := json.MarshalIndent(&info, "", " ")
if err != nil {
return fmt.Errorf("artifact digest JSON conversion failed: %w", err)
}
marshalled = append(marshalled, "\n"...)
rootCmd.Print(string(marshalled))
case "yaml":
marshalled, err := yaml.Marshal(&info)
if err != nil {
return fmt.Errorf("artifact digest YAML conversion failed: %w", err)
}
rootCmd.Print(string(marshalled))
default:
logger.Successf("artifact successfully pushed to %s", digestURL)
}
return nil return nil
} }

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.Receiver // notificationv1.Receiver

View File

@@ -30,7 +30,7 @@ import (
"k8s.io/client-go/util/retry" "k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
@@ -111,7 +111,7 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
} }
logger.Successf("%s annotated", reconcile.kind) logger.Successf("%s annotated", reconcile.kind)
if reconcile.kind == notificationv1.AlertKind || reconcile.kind == notificationv1.ReceiverKind { if reconcile.kind == v1beta1.AlertKind || reconcile.kind == v1beta1.ReceiverKind {
if err = wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, if err = wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReconcileReady(ctx, kubeClient, namespacedName, reconcile.object)); err != nil { isReconcileReady(ctx, kubeClient, namespacedName, reconcile.object)); err != nil {
return err return err

View File

@@ -19,7 +19,7 @@ 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 reconcileAlertCmd = &cobra.Command{ var reconcileAlertCmd = &cobra.Command{

View File

@@ -25,7 +25,7 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
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/internal/utils" "github.com/fluxcd/flux2/internal/utils"

View File

@@ -21,7 +21,7 @@ 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 reconcileImageRepositoryCmd = &cobra.Command{ var reconcileImageRepositoryCmd = &cobra.Command{

View File

@@ -25,7 +25,7 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
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/internal/utils" "github.com/fluxcd/flux2/internal/utils"

View File

@@ -19,7 +19,7 @@ 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 resumeAlertCmd = &cobra.Command{ var resumeAlertCmd = &cobra.Command{

View File

@@ -19,7 +19,7 @@ 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 resumeImageRepositoryCmd = &cobra.Command{ var resumeImageRepositoryCmd = &cobra.Command{

View File

@@ -19,7 +19,7 @@ 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 resumeReceiverCmd = &cobra.Command{ var resumeReceiverCmd = &cobra.Command{

View File

@@ -24,56 +24,47 @@ import (
) )
func TestSourceOCI(t *testing.T) { func TestSourceOCI(t *testing.T) {
namespace := allocateNamespace("oci-test")
del, err := execSetupTestNamespace(namespace)
if err != nil {
t.Fatal(err)
}
t.Cleanup(del)
tmpl := map[string]string{"ns": namespace}
cases := []struct { cases := []struct {
args string args string
goldenFile string goldenFile string
tmpl map[string]string
}{ }{
{ {
"create source oci thrfg --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m", "create source oci thrfg --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m",
"testdata/oci/create_source_oci.golden", "testdata/oci/create_source_oci.golden",
nil,
}, },
{ {
"get source oci thrfg", "get source oci thrfg",
"testdata/oci/get_oci.golden", "testdata/oci/get_oci.golden",
nil,
}, },
{ {
"reconcile source oci thrfg", "reconcile source oci thrfg",
"testdata/oci/reconcile_oci.golden", "testdata/oci/reconcile_oci.golden",
tmpl,
}, },
{ {
"suspend source oci thrfg", "suspend source oci thrfg",
"testdata/oci/suspend_oci.golden", "testdata/oci/suspend_oci.golden",
tmpl,
}, },
{ {
"resume source oci thrfg", "resume source oci thrfg",
"testdata/oci/resume_oci.golden", "testdata/oci/resume_oci.golden",
tmpl,
}, },
{ {
"delete source oci thrfg --silent", "delete source oci thrfg --silent",
"testdata/oci/delete_oci.golden", "testdata/oci/delete_oci.golden",
tmpl,
}, },
} }
namespace := allocateNamespace("oci-test")
del, err := setupTestNamespace(namespace)
if err != nil {
t.Fatal(err)
}
defer del()
for _, tc := range cases { for _, tc := range cases {
cmd := cmdTestCase{ cmd := cmdTestCase{
args: tc.args + " -n=" + namespace, args: tc.args + " -n=" + namespace,
assert: assertGoldenTemplateFile(tc.goldenFile, tc.tmpl), assert: assertGoldenTemplateFile(tc.goldenFile, map[string]string{"ns": namespace}),
} }
cmd.runTestCmd(t) cmd.runTestCmd(t)
} }

View File

@@ -1,219 +0,0 @@
/*
Copyright 2023 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/printers"
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/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"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"
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
"sigs.k8s.io/controller-runtime/pkg/client"
)
var statsCmd = &cobra.Command{
Use: "stats",
Short: "Stats of Flux reconciles",
Long: `The stats command prints a report of Flux custom resources present on a cluster,
including their reconcile status and the amount of cumulative storage used for each source type`,
Example: ` # Print the stats report for a namespace
flux stats --namespace default
# Print the stats report for the whole cluster
flux stats -A`,
RunE: runStatsCmd,
}
type StatsFlags struct {
allNamespaces bool
}
var statsArgs StatsFlags
func init() {
statsCmd.PersistentFlags().BoolVarP(&statsArgs.allNamespaces, "all-namespaces", "A", false,
"list the statistics for objects across all namespaces")
rootCmd.AddCommand(statsCmd)
}
func runStatsCmd(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
}
types := []metav1.GroupVersionKind{
{
Kind: sourcev1.GitRepositoryKind,
Version: sourcev1.GroupVersion.Version,
Group: sourcev1.GroupVersion.Group,
},
{
Kind: sourcev1.OCIRepositoryKind,
Version: sourcev1.GroupVersion.Version,
Group: sourcev1.GroupVersion.Group,
},
{
Kind: sourcev1.HelmRepositoryKind,
Version: sourcev1.GroupVersion.Version,
Group: sourcev1.GroupVersion.Group,
},
{
Kind: sourcev1.HelmChartKind,
Version: sourcev1.GroupVersion.Version,
Group: sourcev1.GroupVersion.Group,
},
{
Kind: sourcev1.BucketKind,
Version: sourcev1.GroupVersion.Version,
Group: sourcev1.GroupVersion.Group,
},
{
Kind: kustomizev1.KustomizationKind,
Version: kustomizev1.GroupVersion.Version,
Group: kustomizev1.GroupVersion.Group,
},
{
Kind: helmv2.HelmReleaseKind,
Version: helmv2.GroupVersion.Version,
Group: helmv2.GroupVersion.Group,
},
{
Kind: notificationv1.AlertKind,
Version: notificationv1.GroupVersion.Version,
Group: notificationv1.GroupVersion.Group,
},
{
Kind: notificationv1.ProviderKind,
Version: notificationv1.GroupVersion.Version,
Group: notificationv1.GroupVersion.Group,
},
{
Kind: notificationv1.ReceiverKind,
Version: notificationv1.GroupVersion.Version,
Group: notificationv1.GroupVersion.Group,
},
{
Kind: autov1.ImageUpdateAutomationKind,
Version: autov1.GroupVersion.Version,
Group: autov1.GroupVersion.Group,
},
{
Kind: imagev1.ImagePolicyKind,
Version: imagev1.GroupVersion.Version,
Group: imagev1.GroupVersion.Group,
},
{
Kind: imagev1.ImageRepositoryKind,
Version: imagev1.GroupVersion.Version,
Group: imagev1.GroupVersion.Group,
},
}
header := []string{"Reconcilers", "Running", "Failing", "Suspended", "Storage"}
var rows [][]string
for _, t := range types {
var total int
var suspended int
var failing int
var totalSize int64
list := unstructured.UnstructuredList{
Object: map[string]interface{}{
"apiVersion": t.Group + "/" + t.Version,
"kind": t.Kind,
},
}
scope := client.InNamespace("")
if !statsArgs.allNamespaces {
scope = client.InNamespace(*kubeconfigArgs.Namespace)
}
if err := kubeClient.List(ctx, &list, scope); err == nil {
total = len(list.Items)
for _, item := range list.Items {
if s, _, _ := unstructured.NestedBool(item.Object, "spec", "suspend"); s {
suspended++
}
if obj, err := status.GetObjectWithConditions(item.Object); err == nil {
for _, cond := range obj.Status.Conditions {
if cond.Type == "Ready" && cond.Status == corev1.ConditionFalse {
failing++
}
}
}
if size, found, _ := unstructured.NestedInt64(item.Object, "status", "artifact", "size"); found {
totalSize += size
}
}
}
rows = append(rows, []string{
t.Kind,
formatInt(total - suspended),
formatInt(failing),
formatInt(suspended),
formatSize(totalSize),
})
}
err = printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows)
if err != nil {
return err
}
return nil
}
func formatInt(i int) string {
return fmt.Sprintf("%d", i)
}
func formatSize(b int64) string {
if b == 0 {
return "-"
}
const unit = 1024
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %ciB",
float64(b)/float64(div), "KMGTPE"[exp])
}

View File

@@ -19,7 +19,7 @@ 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 suspendAlertCmd = &cobra.Command{ var suspendAlertCmd = &cobra.Command{

View File

@@ -19,7 +19,7 @@ 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 suspendImageRepositoryCmd = &cobra.Command{ var suspendImageRepositoryCmd = &cobra.Command{

View File

@@ -19,7 +19,7 @@ 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 suspendReceiverCmd = &cobra.Command{ var suspendReceiverCmd = &cobra.Command{

View File

@@ -1,4 +1,4 @@
apiVersion: autoscaling/v2 apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler kind: HorizontalPodAutoscaler
metadata: metadata:
name: podinfo name: podinfo

View File

@@ -77,7 +77,7 @@ spec:
cpu: 100m cpu: 100m
memory: 64Mi memory: 64Mi
--- ---
apiVersion: autoscaling/v2 apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler kind: HorizontalPodAutoscaler
metadata: metadata:
labels: labels:

View File

@@ -77,7 +77,7 @@ spec:
cpu: 100m cpu: 100m
memory: 64Mi memory: 64Mi
--- ---
apiVersion: autoscaling/v2 apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler kind: HorizontalPodAutoscaler
metadata: metadata:
labels: labels:

View File

@@ -1,4 +1,4 @@
apiVersion: autoscaling/v2 apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler kind: HorizontalPodAutoscaler
metadata: metadata:
name: podinfo name: podinfo

View File

@@ -1,78 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: podinfo-diff
namespace: default
spec:
minReadySeconds: 3
revisionHistoryLimit: 5
progressDeadlineSeconds: 60
strategy:
rollingUpdate:
maxUnavailable: 0
type: RollingUpdate
selector:
matchLabels:
app: podinfo
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9797"
labels:
app: podinfo
spec:
containers:
- name: podinfod
image: ghcr.io/stefanprodan/podinfo:6.0.10
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 9898
protocol: TCP
- name: http-metrics
containerPort: 9797
protocol: TCP
- name: grpc
containerPort: 9999
protocol: TCP
command:
- ./podinfo
- --port=9898
- --port-metrics=9797
- --grpc-port=9999
- --grpc-service-name=podinfo
- --level=info
- --random-delay=false
- --random-error=false
env:
- name: PODINFO_UI_COLOR
value: "#34577c"
livenessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/healthz
initialDelaySeconds: 5
timeoutSeconds: 5
readinessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/readyz
initialDelaySeconds: 5
timeoutSeconds: 5
resources:
limits:
cpu: 2000m
memory: 512Mi
requests:
cpu: 100m
memory: 64Mi

View File

@@ -1,78 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: podinfo
namespace: default
spec:
minReadySeconds: 3
revisionHistoryLimit: 5
progressDeadlineSeconds: 60
strategy:
rollingUpdate:
maxUnavailable: 0
type: RollingUpdate
selector:
matchLabels:
app: podinfo
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9797"
labels:
app: podinfo
spec:
containers:
- name: podinfod
image: ghcr.io/stefanprodan/podinfo:6.0.10
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 9898
protocol: TCP
- name: http-metrics
containerPort: 9797
protocol: TCP
- name: grpc
containerPort: 9999
protocol: TCP
command:
- ./podinfo
- --port=9898
- --port-metrics=9797
- --grpc-port=9999
- --grpc-service-name=podinfo
- --level=info
- --random-delay=false
- --random-error=false
env:
- name: PODINFO_UI_COLOR
value: "#34577c"
livenessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/healthz
initialDelaySeconds: 5
timeoutSeconds: 5
readinessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/readyz
initialDelaySeconds: 5
timeoutSeconds: 5
resources:
limits:
cpu: 2000m
memory: 512Mi
requests:
cpu: 100m
memory: 64Mi

View File

@@ -1 +0,0 @@
✔ no changes detected

View File

@@ -1,5 +1,5 @@
--- ---
apiVersion: notification.toolkit.fluxcd.io/v1beta2 apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Alert kind: Alert
metadata: metadata:
name: flux-system name: flux-system

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