1
0
mirror of synced 2026-03-02 11:36:56 +00:00

Compare commits

..

2 Commits

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

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@58c146cc91c5b9e778e71775dfe9bf1442ad9a12 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@58c146cc91c5b9e778e71775dfe9bf1442ad9a12 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
@@ -223,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@15c905b16b06416d2086efa066dd8e3a35cc7f98 # 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@9754a253a8673b0ea869c2e863b4e975497efd0c # 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 ::set-output name=VERSION::${VERSION}
- 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 }}/${{ 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@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,62 +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@6cffaa064112e1cf9b5798c6224f9487dc1ec316 # 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: Build manifests - name: Build manifests
run: | run: |
make cmd/flux/.manifests.done make cmd/flux/.manifests.done
- name: Run Snyk to check for vulnerabilities - name: Run Snyk to check for vulnerabilities
uses: snyk/actions/golang@e25b2e6f5658d1bb7a6671b113260f13134cc3af # v0.3.0 uses: snyk/actions/golang@master
continue-on-error: true continue-on-error: true
env: env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with: with:
args: --sarif-file-output=snyk.sarif 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@3ebbd71c74ef574dbc558c82f70e52732c8b44fe # 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@3ebbd71c74ef574dbc558c82f70e52732c8b44fe # v2 uses: github/codeql-action/init@v2
with: with:
languages: go languages: go
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@3ebbd71c74ef574dbc558c82f70e52732c8b44fe # v2 uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@3ebbd71c74ef574dbc558c82f70e52732c8b44fe # 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,20 +7,14 @@ 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
@@ -75,7 +69,7 @@ jobs:
- 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`:
@@ -66,7 +52,7 @@ jobs:
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
@@ -76,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
@@ -134,25 +120,18 @@ jobs:
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:
@@ -162,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)/$(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
@@ -199,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

@@ -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"
@@ -128,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()
@@ -173,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{
@@ -221,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
} }
@@ -251,24 +243,19 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
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,19 +24,21 @@ 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{
@@ -60,12 +62,6 @@ command will perform an upgrade if needed.`,
# 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> 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>
# 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
`, `,
RunE: bootstrapGitCmdRun, RunE: bootstrapGitCmdRun,
} }
@@ -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
@@ -275,21 +235,26 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
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
@@ -302,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"
@@ -132,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()
@@ -163,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{
@@ -212,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
} }
@@ -237,23 +232,19 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
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"
@@ -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
} }
@@ -254,23 +246,19 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
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())
defer 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{

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

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

@@ -259,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"
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 {

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

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

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

@@ -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,7 +31,7 @@ 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
@@ -43,28 +40,6 @@ The command can read the credentials from '~/.docker/config.json' but they can a
--source="$(git config --get remote.origin.url)" \ --source="$(git config --get remote.origin.url)" \
--revision="$(git branch --show-current)/$(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)/$(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)/$(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)/$(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) \
@@ -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()
@@ -116,8 +90,6 @@ func init() {
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

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

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

View File

@@ -4,7 +4,7 @@ kind: Namespace
metadata: metadata:
name: {{ .fluxns }} name: {{ .fluxns }}
--- ---
apiVersion: notification.toolkit.fluxcd.io/v1beta2 apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Provider kind: Provider
metadata: metadata:
name: slack name: slack
@@ -14,7 +14,7 @@ spec:
channel: 'A channel with spacess' channel: 'A channel with spacess'
address: https://hooks.slack.com/services/mock address: https://hooks.slack.com/services/mock
--- ---
apiVersion: notification.toolkit.fluxcd.io/v1beta2 apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Alert kind: Alert
metadata: metadata:
name: flux-system name: flux-system
@@ -97,7 +97,7 @@ spec:
interval: 5m interval: 5m
prune: true prune: true
--- ---
apiVersion: notification.toolkit.fluxcd.io/v1beta2 apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Receiver kind: Receiver
metadata: metadata:
name: flux-system name: flux-system

View File

@@ -1,5 +1,5 @@
--- ---
apiVersion: notification.toolkit.fluxcd.io/v1beta2 apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Provider kind: Provider
metadata: metadata:
name: slack name: slack

View File

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

View File

@@ -2,4 +2,4 @@
✔ OCIRepository created ✔ OCIRepository created
◎ waiting for OCIRepository reconciliation ◎ waiting for OCIRepository reconciliation
✔ OCIRepository reconciliation completed ✔ OCIRepository reconciliation completed
✔ fetched revision: 6.1.6/dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3 ✔ fetched revision: dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3

View File

@@ -1,2 +1,2 @@
NAME REVISION SUSPENDED READY MESSAGE NAME REVISION SUSPENDED READY MESSAGE
thrfg 6.1.6/dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3 False True stored artifact for digest '6.1.6/dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3' thrfg dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3 False True stored artifact for digest 'dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3'

View File

@@ -1,4 +1,4 @@
► annotating OCIRepository thrfg in {{ .ns }} namespace ► annotating OCIRepository thrfg in {{ .ns }} namespace
✔ OCIRepository annotated ✔ OCIRepository annotated
◎ waiting for OCIRepository reconciliation ◎ waiting for OCIRepository reconciliation
✔ fetched revision 6.1.6/dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3 ✔ fetched revision dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3

View File

@@ -2,4 +2,4 @@
✔ source oci resumed ✔ source oci resumed
◎ waiting for OCIRepository reconciliation ◎ waiting for OCIRepository reconciliation
✔ OCIRepository reconciliation completed ✔ OCIRepository reconciliation completed
✔ fetched revision 6.1.6/dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3 ✔ fetched revision dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3

View File

@@ -26,21 +26,18 @@ import (
"io" "io"
"strings" "strings"
"github.com/fluxcd/flux2/internal/tree"
"github.com/fluxcd/flux2/internal/utils"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/ssa"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/ssa"
"github.com/fluxcd/flux2/internal/tree"
"github.com/fluxcd/flux2/internal/utils"
) )
var treeKsCmd = &cobra.Command{ var treeKsCmd = &cobra.Command{
@@ -265,7 +262,6 @@ func getHelmReleaseInventory(ctx context.Context, objectKey client.ObjectKey, ku
b = b2 b = b2
} }
// extract objects from Helm storage
var rls hrStorage var rls hrStorage
if err := json.Unmarshal(b, &rls); err != nil { if err := json.Unmarshal(b, &rls); err != nil {
return nil, fmt.Errorf("failed to decode the Helm storage object for HelmRelease '%s': %w", objectKey.String(), err) return nil, fmt.Errorf("failed to decode the Helm storage object for HelmRelease '%s': %w", objectKey.String(), err)
@@ -276,52 +272,5 @@ func getHelmReleaseInventory(ctx context.Context, objectKey client.ObjectKey, ku
return nil, fmt.Errorf("failed to read the Helm storage object for HelmRelease '%s': %w", objectKey.String(), err) return nil, fmt.Errorf("failed to read the Helm storage object for HelmRelease '%s': %w", objectKey.String(), err)
} }
// set the namespace on namespaced objects return object.UnstructuredSetToObjMetadataSet(objects), nil
for _, obj := range objects {
if obj.GetNamespace() == "" {
if isNamespaced, _ := utils.IsAPINamespaced(obj, kubeClient.Scheme(), kubeClient.RESTMapper()); isNamespaced {
if hr.Spec.TargetNamespace != "" {
obj.SetNamespace(hr.Spec.TargetNamespace)
} else {
obj.SetNamespace(hr.GetNamespace())
}
}
}
}
result := object.UnstructuredSetToObjMetadataSet(objects)
// search for CRDs managed by the HelmRelease if installing or upgrading CRDs is enabled in spec
if (hr.Spec.Install != nil && len(hr.Spec.Install.CRDs) > 0 && hr.Spec.Install.CRDs != helmv2.Skip) ||
(hr.Spec.Upgrade != nil && len(hr.Spec.Upgrade.CRDs) > 0 && hr.Spec.Upgrade.CRDs != helmv2.Skip) {
selector := client.MatchingLabels{
fmt.Sprintf("%s/name", helmv2.GroupVersion.Group): hr.GetName(),
fmt.Sprintf("%s/namespace", helmv2.GroupVersion.Group): hr.GetNamespace(),
}
crdKind := "CustomResourceDefinition"
var list apiextensionsv1.CustomResourceDefinitionList
if err := kubeClient.List(ctx, &list, selector); err == nil {
for _, crd := range list.Items {
found := false
for _, r := range result {
if r.Name == crd.GetName() && r.GroupKind.Kind == crdKind {
found = true
break
}
}
if !found {
result = append(result, object.ObjMetadata{
Name: crd.GetName(),
GroupKind: schema.GroupKind{
Group: apiextensionsv1.GroupName,
Kind: crdKind,
},
})
}
}
}
}
return result, nil
} }

View File

@@ -22,9 +22,22 @@ import (
"github.com/manifoldco/promptui" "github.com/manifoldco/promptui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/uninstall" "github.com/fluxcd/flux2/pkg/manifestgen"
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/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
) )
var uninstallCmd = &cobra.Command{ var uninstallCmd = &cobra.Command{
@@ -78,18 +91,317 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
} }
logger.Actionf("deleting components in %s namespace", *kubeconfigArgs.Namespace) logger.Actionf("deleting components in %s namespace", *kubeconfigArgs.Namespace)
uninstall.Components(ctx, logger, kubeClient, *kubeconfigArgs.Namespace, uninstallArgs.dryRun) uninstallComponents(ctx, kubeClient, *kubeconfigArgs.Namespace, uninstallArgs.dryRun)
logger.Actionf("deleting toolkit.fluxcd.io finalizers in all namespaces") logger.Actionf("deleting toolkit.fluxcd.io finalizers in all namespaces")
uninstall.Finalizers(ctx, logger, kubeClient, uninstallArgs.dryRun) uninstallFinalizers(ctx, kubeClient, uninstallArgs.dryRun)
logger.Actionf("deleting toolkit.fluxcd.io custom resource definitions") logger.Actionf("deleting toolkit.fluxcd.io custom resource definitions")
uninstall.CustomResourceDefinitions(ctx, logger, kubeClient, uninstallArgs.dryRun) uninstallCustomResourceDefinitions(ctx, kubeClient, uninstallArgs.dryRun)
if !uninstallArgs.keepNamespace { if !uninstallArgs.keepNamespace {
uninstall.Namespace(ctx, logger, kubeClient, *kubeconfigArgs.Namespace, uninstallArgs.dryRun) uninstallNamespace(ctx, kubeClient, *kubeconfigArgs.Namespace, uninstallArgs.dryRun)
} }
logger.Successf("uninstall finished") logger.Successf("uninstall finished")
return nil return nil
} }
func uninstallComponents(ctx context.Context, kubeClient client.Client, namespace string, dryRun bool) {
opts, dryRunStr := getDeleteOptions(dryRun)
selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}
{
var list appsv1.DeploymentList
if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {
for _, r := range list.Items {
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
logger.Failuref("Deployment/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error())
} else {
logger.Successf("Deployment/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list corev1.ServiceList
if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {
for _, r := range list.Items {
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
logger.Failuref("Service/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error())
} else {
logger.Successf("Service/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list networkingv1.NetworkPolicyList
if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {
for _, r := range list.Items {
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
logger.Failuref("NetworkPolicy/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error())
} else {
logger.Successf("NetworkPolicy/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list corev1.ServiceAccountList
if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {
for _, r := range list.Items {
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
logger.Failuref("ServiceAccount/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error())
} else {
logger.Successf("ServiceAccount/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list rbacv1.ClusterRoleList
if err := kubeClient.List(ctx, &list, selector); err == nil {
for _, r := range list.Items {
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
logger.Failuref("ClusterRole/%s deletion failed: %s", r.Name, err.Error())
} else {
logger.Successf("ClusterRole/%s deleted %s", r.Name, dryRunStr)
}
}
}
}
{
var list rbacv1.ClusterRoleBindingList
if err := kubeClient.List(ctx, &list, selector); err == nil {
for _, r := range list.Items {
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
logger.Failuref("ClusterRoleBinding/%s deletion failed: %s", r.Name, err.Error())
} else {
logger.Successf("ClusterRoleBinding/%s deleted %s", r.Name, dryRunStr)
}
}
}
}
}
func uninstallFinalizers(ctx context.Context, kubeClient client.Client, dryRun bool) {
opts, dryRunStr := getUpdateOptions(dryRun)
{
var list sourcev1.GitRepositoryList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list sourcev1.OCIRepositoryList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list sourcev1.HelmRepositoryList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list sourcev1.HelmChartList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list sourcev1.BucketList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list kustomizev1.KustomizationList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list helmv2.HelmReleaseList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list notificationv1.AlertList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list notificationv1.ProviderList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list notificationv1.ReceiverList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list imagev1.ImagePolicyList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list imagev1.ImageRepositoryList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
{
var list autov1.ImageUpdateAutomationList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
for _, r := range list.Items {
r.Finalizers = []string{}
if err := kubeClient.Update(ctx, &r, opts); err != nil {
logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
} else {
logger.Successf("%s/%s/%s finalizers deleted %s", r.Kind, r.Namespace, r.Name, dryRunStr)
}
}
}
}
}
func uninstallCustomResourceDefinitions(ctx context.Context, kubeClient client.Client, dryRun bool) {
opts, dryRunStr := getDeleteOptions(dryRun)
selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}
{
var list apiextensionsv1.CustomResourceDefinitionList
if err := kubeClient.List(ctx, &list, selector); err == nil {
for _, r := range list.Items {
if err := kubeClient.Delete(ctx, &r, opts); err != nil {
logger.Failuref("CustomResourceDefinition/%s deletion failed: %s", r.Name, err.Error())
} else {
logger.Successf("CustomResourceDefinition/%s deleted %s", r.Name, dryRunStr)
}
}
}
}
}
func uninstallNamespace(ctx context.Context, kubeClient client.Client, namespace string, dryRun bool) {
opts, dryRunStr := getDeleteOptions(dryRun)
ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
if err := kubeClient.Delete(ctx, &ns, opts); err != nil {
logger.Failuref("Namespace/%s deletion failed: %s", namespace, err.Error())
} else {
logger.Successf("Namespace/%s deleted %s", namespace, dryRunStr)
}
}
func getDeleteOptions(dryRun bool) (*client.DeleteOptions, string) {
opts := &client.DeleteOptions{}
var dryRunStr string
if dryRun {
client.DryRunAll.ApplyToDelete(opts)
dryRunStr = "(dry run)"
}
return opts, dryRunStr
}
func getUpdateOptions(dryRun bool) (*client.UpdateOptions, string) {
opts := &client.UpdateOptions{}
var dryRunStr string
if dryRun {
client.DryRunAll.ApplyToUpdate(opts)
dryRunStr = "(dry run)"
}
return opts, dryRunStr
}

215
go.mod
View File

@@ -3,159 +3,132 @@ module github.com/fluxcd/flux2
go 1.18 go 1.18
require ( require (
github.com/Masterminds/semver/v3 v3.2.0 github.com/Masterminds/semver/v3 v3.1.1
github.com/ProtonMail/go-crypto v0.0.0-20230127202215-f7f10de891d5 github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895
github.com/cyphar/filepath-securejoin v0.2.3 github.com/cyphar/filepath-securejoin v0.2.3
github.com/distribution/distribution/v3 v3.0.0-20230131081513-cf87e8d07e8d github.com/fluxcd/go-git-providers v0.8.0
github.com/fluxcd/go-git-providers v0.13.0 github.com/fluxcd/helm-controller/api v0.24.0
github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4 github.com/fluxcd/image-automation-controller/api v0.25.0
github.com/fluxcd/helm-controller/api v0.29.0 github.com/fluxcd/image-reflector-controller/api v0.21.0
github.com/fluxcd/image-automation-controller/api v0.29.0 github.com/fluxcd/kustomize-controller/api v0.28.0
github.com/fluxcd/image-reflector-controller/api v0.24.0 github.com/fluxcd/notification-controller/api v0.26.0
github.com/fluxcd/kustomize-controller/api v0.33.0 github.com/fluxcd/pkg/apis/meta v0.15.0
github.com/fluxcd/notification-controller/api v0.31.0 github.com/fluxcd/pkg/kustomize v0.7.0
github.com/fluxcd/pkg/apis/meta v0.19.0 github.com/fluxcd/pkg/oci v0.9.0
github.com/fluxcd/pkg/git v0.8.0 github.com/fluxcd/pkg/runtime v0.18.0
github.com/fluxcd/pkg/git/gogit v0.5.0 github.com/fluxcd/pkg/sourceignore v0.2.0
github.com/fluxcd/pkg/kustomize v0.13.0 github.com/fluxcd/pkg/ssa v0.19.0
github.com/fluxcd/pkg/oci v0.18.0 github.com/fluxcd/pkg/ssh v0.6.0
github.com/fluxcd/pkg/runtime v0.27.0
github.com/fluxcd/pkg/sourceignore v0.3.0
github.com/fluxcd/pkg/ssa v0.23.0
github.com/fluxcd/pkg/ssh v0.7.0
github.com/fluxcd/pkg/untar v0.2.0 github.com/fluxcd/pkg/untar v0.2.0
github.com/fluxcd/pkg/version v0.2.0 github.com/fluxcd/pkg/version v0.2.0
github.com/fluxcd/source-controller/api v0.34.0 github.com/fluxcd/source-controller/api v0.29.0
github.com/go-git/go-git/v5 v5.4.2
github.com/gonvenience/bunt v1.3.4 github.com/gonvenience/bunt v1.3.4
github.com/gonvenience/ytbx v1.4.4 github.com/gonvenience/ytbx v1.4.4
github.com/google/go-cmp v0.5.9 github.com/google/go-cmp v0.5.8
github.com/google/go-containerregistry v0.13.0 github.com/google/go-containerregistry v0.11.0
github.com/homeport/dyff v1.5.6 github.com/hashicorp/go-multierror v1.1.1
github.com/homeport/dyff v1.5.5
github.com/lucasb-eyer/go-colorful v1.2.0 github.com/lucasb-eyer/go-colorful v1.2.0
github.com/manifoldco/promptui v0.9.0 github.com/manifoldco/promptui v0.9.0
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/gomega v1.26.0 github.com/onsi/gomega v1.20.1
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/spf13/cobra v1.5.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/theckman/yacspin v0.13.12 github.com/theckman/yacspin v0.13.12
golang.org/x/crypto v0.5.0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d
golang.org/x/term v0.4.0 golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
k8s.io/api v0.26.1 k8s.io/api v0.25.0
k8s.io/apiextensions-apiserver v0.26.1 k8s.io/apiextensions-apiserver v0.25.0
k8s.io/apimachinery v0.26.1 k8s.io/apimachinery v0.25.0
k8s.io/cli-runtime v0.26.1 k8s.io/cli-runtime v0.25.0
k8s.io/client-go v0.26.1 k8s.io/client-go v0.25.0
k8s.io/kubectl v0.26.1 k8s.io/kubectl v0.25.0
sigs.k8s.io/cli-utils v0.34.0 sigs.k8s.io/cli-utils v0.33.0
sigs.k8s.io/controller-runtime v0.14.2 sigs.k8s.io/controller-runtime v0.12.3
sigs.k8s.io/kustomize/api v0.12.1 sigs.k8s.io/kustomize/api v0.12.1
sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/kustomize/kyaml v0.13.9
sigs.k8s.io/yaml v1.3.0 sigs.k8s.io/yaml v1.3.0
) )
// Fix CVE-2022-32149
replace golang.org/x/text => golang.org/x/text v0.4.0
// Fix CVE-2022-28948 // Fix CVE-2022-28948
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1 replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
require ( require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0 // indirect cloud.google.com/go v0.99.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect
github.com/BurntSushi/toml v1.0.0 // indirect github.com/BurntSushi/toml v1.0.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/aws/aws-sdk-go-v2 v1.17.3 // indirect github.com/aws/aws-sdk-go v1.44.84 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.18.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.2 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd // indirect
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cloudflare/circl v1.3.1 // indirect github.com/cloudflare/circl v1.1.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.20+incompatible // indirect github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.20+incompatible // indirect github.com/docker/docker v20.10.17+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46 // indirect
github.com/docker/go-metrics v0.0.1 // indirect github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect github.com/emirpasic/gods v1.12.0 // indirect
github.com/drone/envsubst v1.0.3 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/emicklei/go-restful/v3 v3.10.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fatih/color v1.13.0 // indirect github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect
github.com/fluxcd/pkg/apis/kustomize v0.8.0 // indirect github.com/fluxcd/pkg/apis/kustomize v0.5.0 // indirect
github.com/fluxcd/pkg/tar v0.2.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-errors/errors v1.0.1 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/go-git/go-git/v5 v5.4.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.22.3 // indirect github.com/go-openapi/swag v0.21.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.4.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/gomodule/redigo v1.8.2 // indirect
github.com/gonvenience/neat v1.3.11 // indirect github.com/gonvenience/neat v1.3.11 // indirect
github.com/gonvenience/term v1.0.2 // indirect github.com/gonvenience/term v1.0.2 // indirect
github.com/gonvenience/text v1.0.7 // indirect github.com/gonvenience/text v1.0.7 // indirect
github.com/gonvenience/wrap v1.1.2 // indirect github.com/gonvenience/wrap v1.1.2 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic v0.6.9 // indirect github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-github/v48 v48.2.0 // indirect github.com/google/go-github/v45 v45.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect github.com/imdario/mergo v0.3.12 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/klauspost/compress v1.15.11 // indirect github.com/klauspost/compress v1.15.8 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
@@ -163,50 +136,44 @@ require (
github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/hashstructure v1.1.0 // indirect github.com/mitchellh/hashstructure v1.1.0 // indirect
github.com/moby/spdystream v0.2.0 // indirect github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pjbgf/sha1cd v0.2.3 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect
github.com/texttheater/golang-levenshtein v1.0.1 // indirect github.com/texttheater/golang-levenshtein v1.0.1 // indirect
github.com/vbatts/tar-split v0.11.2 // indirect github.com/vbatts/tar-split v0.11.2 // indirect
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect
github.com/xanzy/go-gitlab v0.77.0 // indirect github.com/xanzy/go-gitlab v0.69.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect
github.com/xlab/treeprint v1.1.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c // indirect
go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/mod v0.7.0 // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/net v0.5.0 // indirect golang.org/x/text v0.3.7 // indirect
golang.org/x/oauth2 v0.2.0 // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.4.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
@@ -214,10 +181,10 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/component-base v0.26.1 // indirect k8s.io/component-base v0.25.0 // indirect
k8s.io/klog/v2 v2.90.0 // indirect k8s.io/klog/v2 v2.70.1 // indirect
k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70 // indirect k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
) )

1121
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -9,9 +9,6 @@ To install the latest release run:
curl -s https://raw.githubusercontent.com/fluxcd/flux2/main/install/flux.sh | sudo bash curl -s https://raw.githubusercontent.com/fluxcd/flux2/main/install/flux.sh | sudo bash
``` ```
**Note**: You may want to export the `GITHUB_TOKEN` environment variable using a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)
to avoid GitHub API rate-limiting errors if executing the install script repeatedly during a short time frame.
The install script does the following: The install script does the following:
* attempts to detect your OS * attempts to detect your OS
* downloads and unpacks the release tar file in a temporary directory * downloads and unpacks the release tar file in a temporary directory

View File

@@ -112,10 +112,10 @@ download() {
case $DOWNLOADER in case $DOWNLOADER in
curl) curl)
curl -u user:$GITHUB_TOKEN -o "$1" -sfL "$2" curl -o "$1" -sfL "$2"
;; ;;
wget) wget)
wget --auth-no-challenge --user=user --password=$GITHUB_TOKEN -qO "$1" "$2" wget -qO "$1" "$2"
;; ;;
*) *)
fatal "Incorrect executable '${DOWNLOADER}'" fatal "Incorrect executable '${DOWNLOADER}'"

View File

@@ -19,14 +19,12 @@ package bootstrap
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/ProtonMail/go-crypto/openpgp" gogit "github.com/go-git/go-git/v5"
gogit "github.com/fluxcd/go-git/v5"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
@@ -40,6 +38,7 @@ import (
"github.com/fluxcd/pkg/kustomize/filesys" "github.com/fluxcd/pkg/kustomize/filesys"
runclient "github.com/fluxcd/pkg/runtime/client" runclient "github.com/fluxcd/pkg/runtime/client"
"github.com/fluxcd/flux2/internal/bootstrap/git"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/log" "github.com/fluxcd/flux2/pkg/log"
"github.com/fluxcd/flux2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen/install"
@@ -47,29 +46,28 @@ import (
"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/flux2/pkg/status" "github.com/fluxcd/flux2/pkg/status"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/repository"
) )
type PlainGitBootstrapper struct { type PlainGitBootstrapper struct {
url string url string
branch string branch string
caBundle []byte
signature git.Signature author git.Author
commitMessageAppendix string commitMessageAppendix string
gpgKeyRing openpgp.EntityList gpgKeyRingPath string
gpgPassphrase string gpgPassphrase string
gpgKeyID string gpgKeyID string
restClientGetter genericclioptions.RESTClientGetter restClientGetter genericclioptions.RESTClientGetter
restClientOptions *runclient.Options restClientOptions *runclient.Options
postGenerateSecret []PostGenerateSecretFunc postGenerateSecret []PostGenerateSecretFunc
gitClient repository.Client git git.Git
kube client.Client kube client.Client
logger log.Logger logger log.Logger
} }
type GitOption interface { type GitOption interface {
@@ -96,10 +94,10 @@ func (o postGenerateSecret) applyGit(b *PlainGitBootstrapper) {
b.postGenerateSecret = append(b.postGenerateSecret, PostGenerateSecretFunc(o)) b.postGenerateSecret = append(b.postGenerateSecret, PostGenerateSecretFunc(o))
} }
func NewPlainGitProvider(git repository.Client, kube client.Client, opts ...GitOption) (*PlainGitBootstrapper, error) { func NewPlainGitProvider(git git.Git, kube client.Client, opts ...GitOption) (*PlainGitBootstrapper, error) {
b := &PlainGitBootstrapper{ b := &PlainGitBootstrapper{
gitClient: git, git: git,
kube: kube, kube: kube,
} }
for _, opt := range opts { for _, opt := range opts {
opt.applyGit(b) opt.applyGit(b)
@@ -109,7 +107,7 @@ func NewPlainGitProvider(git repository.Client, kube client.Client, opts ...GitO
func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifestsBase string, options install.Options, _ sourcesecret.Options) error { func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifestsBase string, options install.Options, _ sourcesecret.Options) error {
// Clone if not already // Clone if not already
if _, err := b.gitClient.Head(); err != nil { if _, err := b.git.Status(); err != nil {
if err != git.ErrNoGitRepository { if err != git.ErrNoGitRepository {
return err return err
} }
@@ -117,17 +115,7 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
b.logger.Actionf("cloning branch %q from Git repository %q", b.branch, b.url) b.logger.Actionf("cloning branch %q from Git repository %q", b.branch, b.url)
var cloned bool var cloned bool
if err = retry(1, 2*time.Second, func() (err error) { if err = retry(1, 2*time.Second, func() (err error) {
_, err = b.gitClient.Clone(ctx, b.url, repository.CloneOptions{ cloned, err = b.git.Clone(ctx, b.url, b.branch, b.caBundle)
CheckoutStrategy: repository.CheckoutStrategy{
Branch: b.branch,
},
})
if err != nil {
b.logger.Warningf(" clone failure: %s", err)
}
if err == nil {
cloned = true
}
return return
}); err != nil { }); err != nil {
return fmt.Errorf("failed to clone repository: %w", err) return fmt.Errorf("failed to clone repository: %w", err)
@@ -145,33 +133,28 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
} }
b.logger.Successf("generated component manifests") b.logger.Successf("generated component manifests")
// Write generated files and make a commit // Write manifest to Git repository
var signer *openpgp.Entity if err = b.git.Write(manifests.Path, strings.NewReader(manifests.Content)); err != nil {
if b.gpgKeyRing != nil { return fmt.Errorf("failed to write manifest %q: %w", manifests.Path, err)
signer, err = getOpenPgpEntity(b.gpgKeyRing, b.gpgPassphrase, b.gpgKeyID)
if err != nil {
return fmt.Errorf("failed to generate OpenPGP entity: %w", err)
}
} }
// Git commit generated
gpgOpts := git.WithGpgSigningOption(b.gpgKeyRingPath, b.gpgPassphrase, b.gpgKeyID)
commitMsg := fmt.Sprintf("Add Flux %s component manifests", options.Version) commitMsg := fmt.Sprintf("Add Flux %s component manifests", options.Version)
if b.commitMessageAppendix != "" { if b.commitMessageAppendix != "" {
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix
} }
commit, err := b.git.Commit(git.Commit{
commit, err := b.gitClient.Commit(git.Commit{ Author: b.author,
Author: b.signature,
Message: commitMsg, Message: commitMsg,
}, repository.WithFiles(map[string]io.Reader{ }, gpgOpts)
manifests.Path: strings.NewReader(manifests.Content),
}), repository.WithSigner(signer))
if err != nil && err != git.ErrNoStagedFiles { if err != nil && err != git.ErrNoStagedFiles {
return fmt.Errorf("failed to commit sync manifests: %w", err) return fmt.Errorf("failed to commit sync manifests: %w", err)
} }
if err == nil { if err == nil {
b.logger.Successf("committed sync manifests to %q (%q)", b.branch, commit) b.logger.Successf("committed sync manifests to %q (%q)", b.branch, commit)
b.logger.Actionf("pushing component manifests to %q", b.url) b.logger.Actionf("pushing component manifests to %q", b.url)
if err = b.gitClient.Push(ctx); err != nil { if err = b.git.Push(ctx, b.caBundle); err != nil {
return fmt.Errorf("failed to push manifests: %w", err) return fmt.Errorf("failed to push manifests: %w", err)
} }
} else { } else {
@@ -182,16 +165,16 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
if mustInstallManifests(ctx, b.kube, options.Namespace) { if mustInstallManifests(ctx, b.kube, options.Namespace) {
b.logger.Actionf("installing components in %q namespace", options.Namespace) b.logger.Actionf("installing components in %q namespace", options.Namespace)
componentsYAML := filepath.Join(b.gitClient.Path(), manifests.Path) componentsYAML := filepath.Join(b.git.Path(), manifests.Path)
kfile := filepath.Join(filepath.Dir(componentsYAML), konfig.DefaultKustomizationFileName()) kfile := filepath.Join(filepath.Dir(componentsYAML), konfig.DefaultKustomizationFileName())
if _, err := os.Stat(kfile); err == nil { if _, err := os.Stat(kfile); err == nil {
// Apply the components and their patches // Apply the components and their patches
if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.gitClient.Path(), kfile); err != nil { if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.git.Path(), kfile); err != nil {
return err return err
} }
} else { } else {
// Apply the CRDs and controllers // Apply the CRDs and controllers
if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.gitClient.Path(), componentsYAML); err != nil { if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.git.Path(), componentsYAML); err != nil {
return err return err
} }
} }
@@ -212,7 +195,7 @@ func (b *PlainGitBootstrapper) ReconcileSourceSecret(ctx context.Context, option
} }
// Return early if exists and no custom config is passed // Return early if exists and no custom config is passed
if ok && options.Keypair == nil && len(options.CAFile) == 0 && len(options.Username+options.Password) == 0 { if ok && len(options.CAFilePath+options.PrivateKeyPath+options.Username+options.Password) == 0 {
b.logger.Successf("source secret up to date") b.logger.Successf("source secret up to date")
return nil return nil
} }
@@ -253,19 +236,12 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
} }
// Clone if not already // Clone if not already
if _, err := b.gitClient.Head(); err != nil { if _, err := b.git.Status(); err != nil {
if err == git.ErrNoGitRepository { if err == git.ErrNoGitRepository {
b.logger.Actionf("cloning branch %q from Git repository %q", b.branch, b.url) b.logger.Actionf("cloning branch %q from Git repository %q", b.branch, b.url)
var cloned bool var cloned bool
if err = retry(1, 2*time.Second, func() (err error) { if err = retry(1, 2*time.Second, func() (err error) {
_, err = b.gitClient.Clone(ctx, b.url, repository.CloneOptions{ cloned, err = b.git.Clone(ctx, b.url, b.branch, b.caBundle)
CheckoutStrategy: repository.CheckoutStrategy{
Branch: b.branch,
},
})
if err == nil {
cloned = true
}
return return
}); err != nil { }); err != nil {
return fmt.Errorf("failed to clone repository: %w", err) return fmt.Errorf("failed to clone repository: %w", err)
@@ -283,47 +259,41 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
if err != nil { if err != nil {
return fmt.Errorf("sync manifests generation failed: %w", err) return fmt.Errorf("sync manifests generation failed: %w", err)
} }
if err = b.git.Write(manifests.Path, strings.NewReader(manifests.Content)); err != nil {
// Create secure Kustomize FS return fmt.Errorf("failed to write manifest %q: %w", manifests.Path, err)
fs, err := filesys.MakeFsOnDiskSecureBuild(b.gitClient.Path())
if err != nil {
return fmt.Errorf("failed to initialize Kustomize file system: %w", err)
} }
if err = fs.WriteFile(filepath.Join(b.gitClient.Path(), manifests.Path), []byte(manifests.Content)); err != nil { // Create secure Kustomize FS
return err fs, err := filesys.MakeFsOnDiskSecureBuild(b.git.Path())
if err != nil {
return fmt.Errorf("failed to initialize Kustomize file system: %w", err)
} }
// Generate Kustomization // Generate Kustomization
kusManifests, err := kustomization.Generate(kustomization.Options{ kusManifests, err := kustomization.Generate(kustomization.Options{
FileSystem: fs, FileSystem: fs,
BaseDir: b.gitClient.Path(), BaseDir: b.git.Path(),
TargetPath: filepath.Dir(manifests.Path), TargetPath: filepath.Dir(manifests.Path),
}) })
if err != nil { if err != nil {
return fmt.Errorf("%s generation failed: %w", konfig.DefaultKustomizationFileName(), err) return fmt.Errorf("%s generation failed: %w", konfig.DefaultKustomizationFileName(), err)
} }
if err = b.git.Write(kusManifests.Path, strings.NewReader(kusManifests.Content)); err != nil {
return fmt.Errorf("failed to write manifest %q: %w", kusManifests.Path, err)
}
b.logger.Successf("generated sync manifests") b.logger.Successf("generated sync manifests")
// Write generated files and make a commit // Git commit generated
var signer *openpgp.Entity gpgOpts := git.WithGpgSigningOption(b.gpgKeyRingPath, b.gpgPassphrase, b.gpgKeyID)
if b.gpgKeyRing != nil {
signer, err = getOpenPgpEntity(b.gpgKeyRing, b.gpgPassphrase, b.gpgKeyID)
if err != nil {
return fmt.Errorf("failed to generate OpenPGP entity: %w", err)
}
}
commitMsg := fmt.Sprintf("Add Flux sync manifests") commitMsg := fmt.Sprintf("Add Flux sync manifests")
if b.commitMessageAppendix != "" { if b.commitMessageAppendix != "" {
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix
} }
commit, err := b.git.Commit(git.Commit{
commit, err := b.gitClient.Commit(git.Commit{ Author: b.author,
Author: b.signature,
Message: commitMsg, Message: commitMsg,
}, repository.WithFiles(map[string]io.Reader{ }, gpgOpts)
kusManifests.Path: strings.NewReader(kusManifests.Content),
}), repository.WithSigner(signer))
if err != nil && err != git.ErrNoStagedFiles { if err != nil && err != git.ErrNoStagedFiles {
return fmt.Errorf("failed to commit sync manifests: %w", err) return fmt.Errorf("failed to commit sync manifests: %w", err)
} }
@@ -331,22 +301,18 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
if err == nil { if err == nil {
b.logger.Successf("committed sync manifests to %q (%q)", b.branch, commit) b.logger.Successf("committed sync manifests to %q (%q)", b.branch, commit)
b.logger.Actionf("pushing sync manifests to %q", b.url) b.logger.Actionf("pushing sync manifests to %q", b.url)
err = b.gitClient.Push(ctx) err = b.git.Push(ctx, b.caBundle)
if err != nil { if err != nil {
if strings.HasPrefix(err.Error(), gogit.ErrNonFastForwardUpdate.Error()) { if strings.HasPrefix(err.Error(), gogit.ErrNonFastForwardUpdate.Error()) {
b.logger.Waitingf("git conflict detected, retrying with a fresh clone") b.logger.Waitingf("git conflict detected, retrying with a fresh clone")
if err := os.RemoveAll(b.gitClient.Path()); err != nil { if err := os.RemoveAll(b.git.Path()); err != nil {
return fmt.Errorf("failed to remove tmp dir: %w", err) return fmt.Errorf("failed to remove tmp dir: %w", err)
} }
if err := os.Mkdir(b.gitClient.Path(), 0o700); err != nil { if err := os.Mkdir(b.git.Path(), 0o700); err != nil {
return fmt.Errorf("failed to recreate tmp dir: %w", err) return fmt.Errorf("failed to recreate tmp dir: %w", err)
} }
if err = retry(1, 2*time.Second, func() (err error) { if err = retry(1, 2*time.Second, func() (err error) {
_, err = b.gitClient.Clone(ctx, b.url, repository.CloneOptions{ _, err = b.git.Clone(ctx, b.url, b.branch, b.caBundle)
CheckoutStrategy: repository.CheckoutStrategy{
Branch: b.branch,
},
})
return return
}); err != nil { }); err != nil {
return fmt.Errorf("failed to clone repository: %w", err) return fmt.Errorf("failed to clone repository: %w", err)
@@ -361,7 +327,7 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
// Apply to cluster // Apply to cluster
b.logger.Actionf("applying sync manifests") b.logger.Actionf("applying sync manifests")
if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.gitClient.Path(), filepath.Join(b.gitClient.Path(), kusManifests.Path)); err != nil { if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.git.Path(), filepath.Join(b.git.Path(), kusManifests.Path)); err != nil {
return err return err
} }
@@ -371,7 +337,7 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
} }
func (b *PlainGitBootstrapper) ReportKustomizationHealth(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error { func (b *PlainGitBootstrapper) ReportKustomizationHealth(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error {
head, err := b.gitClient.Head() head, err := b.git.Head()
if err != nil { if err != nil {
return err return err
} }
@@ -423,42 +389,3 @@ func (b *PlainGitBootstrapper) ReportComponentsHealth(ctx context.Context, insta
b.logger.Successf("all components are healthy") b.logger.Successf("all components are healthy")
return nil return nil
} }
func getOpenPgpEntity(keyRing openpgp.EntityList, passphrase, keyID string) (*openpgp.Entity, error) {
if len(keyRing) == 0 {
return nil, fmt.Errorf("empty GPG key ring")
}
var entity *openpgp.Entity
if keyID != "" {
if strings.HasPrefix(keyID, "0x") {
keyID = strings.TrimPrefix(keyID, "0x")
}
if len(keyID) != 16 {
return nil, fmt.Errorf("invalid GPG key id length; expected %d, got %d", 16, len(keyID))
}
keyID = strings.ToUpper(keyID)
for _, ent := range keyRing {
if ent.PrimaryKey.KeyIdString() == keyID {
entity = ent
}
}
if entity == nil {
return nil, fmt.Errorf("no GPG keyring matching key id '%s' found", keyID)
}
if entity.PrivateKey == nil {
return nil, fmt.Errorf("keyring does not contain private key for key id '%s'", keyID)
}
} else {
entity = keyRing[0]
}
err := entity.PrivateKey.Decrypt([]byte(passphrase))
if err != nil {
return nil, fmt.Errorf("unable to decrypt GPG private key: %w", err)
}
return entity, nil
}

View File

@@ -29,10 +29,10 @@ import (
"github.com/fluxcd/go-git-providers/gitprovider" "github.com/fluxcd/go-git-providers/gitprovider"
"github.com/fluxcd/flux2/pkg/bootstrap/provider" "github.com/fluxcd/flux2/internal/bootstrap/git"
"github.com/fluxcd/flux2/internal/bootstrap/provider"
"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/repository"
) )
type GitProviderBootstrapper struct { type GitProviderBootstrapper struct {
@@ -62,12 +62,11 @@ type GitProviderBootstrapper struct {
provider gitprovider.Client provider gitprovider.Client
} }
func NewGitProviderBootstrapper(git repository.Client, provider gitprovider.Client, func NewGitProviderBootstrapper(git git.Git, provider gitprovider.Client, kube client.Client, opts ...GitProviderOption) (*GitProviderBootstrapper, error) {
kube client.Client, opts ...GitProviderOption) (*GitProviderBootstrapper, error) {
b := &GitProviderBootstrapper{ b := &GitProviderBootstrapper{
PlainGitBootstrapper: &PlainGitBootstrapper{ PlainGitBootstrapper: &PlainGitBootstrapper{
gitClient: git, git: git,
kube: kube, kube: kube,
}, },
bootstrapTransportType: "https", bootstrapTransportType: "https",
syncTransportType: "ssh", syncTransportType: "ssh",

View File

@@ -0,0 +1,42 @@
package git
// Option is a some configuration that modifies options for a commit.
type Option interface {
// ApplyToCommit applies this configuration to a given commit option.
ApplyToCommit(*CommitOptions)
}
// CommitOptions contains options for making a commit.
type CommitOptions struct {
*GPGSigningInfo
}
// GPGSigningInfo contains information for signing a commit.
type GPGSigningInfo struct {
KeyRingPath string
Passphrase string
KeyID string
}
type GpgSigningOption struct {
*GPGSigningInfo
}
func (w GpgSigningOption) ApplyToCommit(in *CommitOptions) {
in.GPGSigningInfo = w.GPGSigningInfo
}
func WithGpgSigningOption(path, passphrase, keyID string) Option {
// Return nil if no path is set, even if other options are configured.
if path == "" {
return GpgSigningOption{}
}
return GpgSigningOption{
GPGSigningInfo: &GPGSigningInfo{
KeyRingPath: path,
Passphrase: passphrase,
KeyID: keyID,
},
}
}

View File

@@ -0,0 +1,52 @@
/*
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 git
import (
"context"
"errors"
"io"
)
var (
ErrNoGitRepository = errors.New("no git repository")
ErrNoStagedFiles = errors.New("no staged files")
)
type Author struct {
Name string
Email string
}
type Commit struct {
Author
Hash string
Message string
}
// Git is an interface for basic Git operations on a single branch of a
// remote repository.
type Git interface {
Init(url, branch string) (bool, error)
Clone(ctx context.Context, url, branch string, caBundle []byte) (bool, error)
Write(path string, reader io.Reader) error
Commit(message Commit, options ...Option) (string, error)
Push(ctx context.Context, caBundle []byte) error
Status() (bool, error)
Head() (string, error)
Path() string
}

View File

@@ -0,0 +1,296 @@
/*
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 gogit
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
gogit "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/fluxcd/flux2/internal/bootstrap/git"
)
type GoGit struct {
path string
auth transport.AuthMethod
repository *gogit.Repository
}
type CommitOptions struct {
GpgKeyPath string
GpgKeyPassphrase string
KeyID string
}
func New(path string, auth transport.AuthMethod) *GoGit {
return &GoGit{
path: path,
auth: auth,
}
}
func (g *GoGit) Init(url, branch string) (bool, error) {
if g.repository != nil {
return false, nil
}
r, err := gogit.PlainInit(g.path, false)
if err != nil {
return false, err
}
if _, err = r.CreateRemote(&config.RemoteConfig{
Name: gogit.DefaultRemoteName,
URLs: []string{url},
}); err != nil {
return false, err
}
branchRef := plumbing.NewBranchReferenceName(branch)
if err = r.CreateBranch(&config.Branch{
Name: branch,
Remote: gogit.DefaultRemoteName,
Merge: branchRef,
}); err != nil {
return false, err
}
// PlainInit assumes the initial branch to always be master, we can
// overwrite this by setting the reference of the Storer to a new
// symbolic reference (as there are no commits yet) that points
// the HEAD to our new branch.
if err = r.Storer.SetReference(plumbing.NewSymbolicReference(plumbing.HEAD, branchRef)); err != nil {
return false, err
}
g.repository = r
return true, nil
}
func (g *GoGit) Clone(ctx context.Context, url, branch string, caBundle []byte) (bool, error) {
branchRef := plumbing.NewBranchReferenceName(branch)
r, err := gogit.PlainCloneContext(ctx, g.path, false, &gogit.CloneOptions{
URL: url,
Auth: g.auth,
RemoteName: gogit.DefaultRemoteName,
ReferenceName: branchRef,
SingleBranch: true,
NoCheckout: false,
Progress: nil,
Tags: gogit.NoTags,
CABundle: caBundle,
})
if err != nil {
if err == transport.ErrEmptyRemoteRepository || isRemoteBranchNotFoundErr(err, branchRef.String()) {
return g.Init(url, branch)
}
return false, err
}
g.repository = r
return true, nil
}
func (g *GoGit) Write(path string, reader io.Reader) error {
if g.repository == nil {
return git.ErrNoGitRepository
}
wt, err := g.repository.Worktree()
if err != nil {
return err
}
f, err := wt.Filesystem.Create(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, reader)
return err
}
func (g *GoGit) Commit(message git.Commit, opts ...git.Option) (string, error) {
if g.repository == nil {
return "", git.ErrNoGitRepository
}
wt, err := g.repository.Worktree()
if err != nil {
return "", err
}
status, err := wt.Status()
if err != nil {
return "", err
}
// apply the options
options := &git.CommitOptions{}
for _, opt := range opts {
opt.ApplyToCommit(options)
}
// go-git has [a bug](https://github.com/go-git/go-git/issues/253)
// whereby it thinks broken symlinks to absolute paths are
// modified. There's no circumstance in which we want to commit a
// change to a broken symlink: so, detect and skip those.
var changed bool
for file, _ := range status {
abspath := filepath.Join(g.path, file)
info, err := os.Lstat(abspath)
if err != nil {
return "", fmt.Errorf("checking if %s is a symlink: %w", file, err)
}
if info.Mode()&os.ModeSymlink > 0 {
// symlinks are OK; broken symlinks are probably a result
// of the bug mentioned above, but not of interest in any
// case.
if _, err := os.Stat(abspath); os.IsNotExist(err) {
continue
}
}
_, _ = wt.Add(file)
changed = true
}
if !changed {
head, err := g.repository.Head()
if err != nil {
return "", err
}
return head.Hash().String(), git.ErrNoStagedFiles
}
commitOpts := &gogit.CommitOptions{
Author: &object.Signature{
Name: message.Name,
Email: message.Email,
When: time.Now(),
},
}
if options.GPGSigningInfo != nil {
entity, err := getOpenPgpEntity(*options.GPGSigningInfo)
if err != nil {
return "", err
}
commitOpts.SignKey = entity
}
commit, err := wt.Commit(message.Message, commitOpts)
if err != nil {
return "", err
}
return commit.String(), nil
}
func (g *GoGit) Push(ctx context.Context, caBundle []byte) error {
if g.repository == nil {
return git.ErrNoGitRepository
}
return g.repository.PushContext(ctx, &gogit.PushOptions{
RemoteName: gogit.DefaultRemoteName,
Auth: g.auth,
Progress: nil,
CABundle: caBundle,
})
}
func (g *GoGit) Status() (bool, error) {
if g.repository == nil {
return false, git.ErrNoGitRepository
}
wt, err := g.repository.Worktree()
if err != nil {
return false, err
}
status, err := wt.Status()
if err != nil {
return false, err
}
return status.IsClean(), nil
}
func (g *GoGit) Head() (string, error) {
if g.repository == nil {
return "", git.ErrNoGitRepository
}
head, err := g.repository.Head()
if err != nil {
return "", err
}
return head.Hash().String(), nil
}
func (g *GoGit) Path() string {
return g.path
}
func isRemoteBranchNotFoundErr(err error, ref string) bool {
return strings.Contains(err.Error(), fmt.Sprintf("couldn't find remote ref %q", ref))
}
func getOpenPgpEntity(info git.GPGSigningInfo) (*openpgp.Entity, error) {
r, err := os.Open(info.KeyRingPath)
if err != nil {
return nil, fmt.Errorf("unable to open GPG key ring: %w", err)
}
entityList, err := openpgp.ReadKeyRing(r)
if err != nil {
return nil, err
}
if len(entityList) == 0 {
return nil, fmt.Errorf("empty GPG key ring")
}
var entity *openpgp.Entity
if info.KeyID != "" {
for _, ent := range entityList {
if ent.PrimaryKey.KeyIdString() == info.KeyID {
entity = ent
}
}
if entity == nil {
return nil, fmt.Errorf("no GPG private key matching key id '%s' found", info.KeyID)
}
} else {
entity = entityList[0]
}
err = entity.PrivateKey.Decrypt([]byte(info.Passphrase))
if err != nil {
return nil, fmt.Errorf("unable to decrypt GPG private key: %w", err)
}
return entity, nil
}

View File

@@ -0,0 +1,67 @@
//go:build unit
// +build unit
package gogit
import (
"testing"
"github.com/fluxcd/flux2/internal/bootstrap/git"
)
func TestGetOpenPgpEntity(t *testing.T) {
tests := []struct {
name string
keyPath string
passphrase string
id string
expectErr bool
}{
{
name: "no default key id given",
keyPath: "testdata/private.key",
passphrase: "flux",
id: "",
expectErr: false,
},
{
name: "key id given",
keyPath: "testdata/private.key",
passphrase: "flux",
id: "0619327DBD777415",
expectErr: false,
},
{
name: "wrong key id",
keyPath: "testdata/private.key",
passphrase: "flux",
id: "0619327DBD777416",
expectErr: true,
},
{
name: "wrong password",
keyPath: "testdata/private.key",
passphrase: "fluxe",
id: "0619327DBD777415",
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gpgInfo := git.GPGSigningInfo{
KeyRingPath: tt.keyPath,
Passphrase: tt.passphrase,
KeyID: tt.id,
}
_, err := getOpenPgpEntity(gpgInfo)
if err != nil && !tt.expectErr {
t.Errorf("unexpected error: %s", err)
}
if err == nil && tt.expectErr {
t.Errorf("expected error when %s", tt.name)
}
})
}
}

Binary file not shown.

View File

@@ -17,15 +17,11 @@ limitations under the License.
package bootstrap package bootstrap
import ( import (
"fmt"
"os"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/fluxcd/pkg/git"
runclient "github.com/fluxcd/pkg/runtime/client" runclient "github.com/fluxcd/pkg/runtime/client"
"github.com/fluxcd/flux2/internal/bootstrap/git"
"github.com/fluxcd/flux2/pkg/log" "github.com/fluxcd/flux2/pkg/log"
) )
@@ -48,28 +44,42 @@ func (o branchOption) applyGitProvider(b *GitProviderBootstrapper) {
o.applyGit(b.PlainGitBootstrapper) o.applyGit(b.PlainGitBootstrapper)
} }
func WithSignature(name, email string) Option { func WithAuthor(name, email string) Option {
return signatureOption{ return authorOption{
Name: name, Name: name,
Email: email, Email: email,
} }
} }
type signatureOption git.Signature type authorOption git.Author
func (o signatureOption) applyGit(b *PlainGitBootstrapper) { func (o authorOption) applyGit(b *PlainGitBootstrapper) {
if o.Name != "" { if o.Name != "" {
b.signature.Name = o.Name b.author.Name = o.Name
} }
if o.Email != "" { if o.Email != "" {
b.signature.Email = o.Email b.author.Email = o.Email
} }
} }
func (o signatureOption) applyGitProvider(b *GitProviderBootstrapper) { func (o authorOption) applyGitProvider(b *GitProviderBootstrapper) {
o.applyGit(b.PlainGitBootstrapper) o.applyGit(b.PlainGitBootstrapper)
} }
func WithCABundle(b []byte) Option {
return caBundleOption(b)
}
type caBundleOption []byte
func (o caBundleOption) applyGit(b *PlainGitBootstrapper) {
b.caBundle = o
}
func (o caBundleOption) applyGitProvider(b *GitProviderBootstrapper) {
b.caBundle = o
}
func WithCommitMessageAppendix(appendix string) Option { func WithCommitMessageAppendix(appendix string) Option {
return commitMessageAppendixOption(appendix) return commitMessageAppendixOption(appendix)
} }
@@ -121,22 +131,22 @@ func (o loggerOption) applyGitProvider(b *GitProviderBootstrapper) {
b.logger = o.logger b.logger = o.logger
} }
func WithGitCommitSigning(gpgKeyRing openpgp.EntityList, passphrase, keyID string) Option { func WithGitCommitSigning(path, passphrase, keyID string) Option {
return gitCommitSigningOption{ return gitCommitSigningOption{
gpgKeyRing: gpgKeyRing, gpgKeyRingPath: path,
gpgPassphrase: passphrase, gpgPassphrase: passphrase,
gpgKeyID: keyID, gpgKeyID: keyID,
} }
} }
type gitCommitSigningOption struct { type gitCommitSigningOption struct {
gpgKeyRing openpgp.EntityList gpgKeyRingPath string
gpgPassphrase string gpgPassphrase string
gpgKeyID string gpgKeyID string
} }
func (o gitCommitSigningOption) applyGit(b *PlainGitBootstrapper) { func (o gitCommitSigningOption) applyGit(b *PlainGitBootstrapper) {
b.gpgKeyRing = o.gpgKeyRing b.gpgKeyRingPath = o.gpgKeyRingPath
b.gpgPassphrase = o.gpgPassphrase b.gpgPassphrase = o.gpgPassphrase
b.gpgKeyID = o.gpgKeyID b.gpgKeyID = o.gpgKeyID
} }
@@ -144,18 +154,3 @@ func (o gitCommitSigningOption) applyGit(b *PlainGitBootstrapper) {
func (o gitCommitSigningOption) applyGitProvider(b *GitProviderBootstrapper) { func (o gitCommitSigningOption) applyGitProvider(b *GitProviderBootstrapper) {
o.applyGit(b.PlainGitBootstrapper) o.applyGit(b.PlainGitBootstrapper)
} }
func LoadEntityListFromPath(path string) (openpgp.EntityList, error) {
if path == "" {
return nil, nil
}
r, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("unable to open GPG key ring: %w", err)
}
entityList, err := openpgp.ReadKeyRing(r)
if err != nil {
return nil, fmt.Errorf("unable to read GPG key ring: %w", err)
}
return entityList, nil
}

View File

@@ -42,8 +42,8 @@ import (
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/kustomize" "github.com/fluxcd/pkg/kustomize"
"github.com/fluxcd/pkg/kustomize/filesys"
runclient "github.com/fluxcd/pkg/runtime/client" runclient "github.com/fluxcd/pkg/runtime/client"
"sigs.k8s.io/kustomize/kyaml/filesys"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
@@ -76,13 +76,10 @@ type Builder struct {
kustomization *kustomizev1.Kustomization kustomization *kustomizev1.Kustomization
timeout time.Duration timeout time.Duration
spinner *yacspin.Spinner spinner *yacspin.Spinner
dryRun bool
} }
// BuilderOptionFunc is a function that configures a Builder
type BuilderOptionFunc func(b *Builder) error type BuilderOptionFunc func(b *Builder) error
// WithKustomizationFile sets the kustomization file
func WithKustomizationFile(file string) BuilderOptionFunc { func WithKustomizationFile(file string) BuilderOptionFunc {
return func(b *Builder) error { return func(b *Builder) error {
b.kustomizationFile = file b.kustomizationFile = file
@@ -90,7 +87,6 @@ func WithKustomizationFile(file string) BuilderOptionFunc {
} }
} }
// WithTimeout sets the timeout for the builder
func WithTimeout(timeout time.Duration) BuilderOptionFunc { func WithTimeout(timeout time.Duration) BuilderOptionFunc {
return func(b *Builder) error { return func(b *Builder) error {
b.timeout = timeout b.timeout = timeout
@@ -120,55 +116,24 @@ func WithProgressBar() BuilderOptionFunc {
} }
} }
// WithClientConfig sets the client configuration
func WithClientConfig(rcg *genericclioptions.ConfigFlags, clientOpts *runclient.Options) BuilderOptionFunc {
return func(b *Builder) error {
kubeClient, err := utils.KubeClient(rcg, clientOpts)
if err != nil {
return err
}
restMapper, err := rcg.ToRESTMapper()
if err != nil {
return err
}
b.client = kubeClient
b.restMapper = restMapper
b.namespace = *rcg.Namespace
return nil
}
}
// WithNamespace sets the namespace
func WithNamespace(namespace string) BuilderOptionFunc {
return func(b *Builder) error {
b.namespace = namespace
return nil
}
}
// WithDryRun sets the dry-run flag
func WithDryRun(dryRun bool) BuilderOptionFunc {
return func(b *Builder) error {
b.dryRun = dryRun
return nil
}
}
// NewBuilder returns a new Builder // NewBuilder returns a new Builder
// It takes a kustomization name and a path to the resources // to dp : create functional options
// It also takes a list of BuilderOptionFunc to configure the builder func NewBuilder(rcg *genericclioptions.ConfigFlags, clientOpts *runclient.Options, name, resources string, opts ...BuilderOptionFunc) (*Builder, error) {
// One of the options is WithClientConfig, that must be provided for the builder to work kubeClient, err := utils.KubeClient(rcg, clientOpts)
// with the k8s cluster if err != nil {
// One other option is WithKustomizationFile, that must be provided for the builder to work return nil, err
// with a local kustomization file. If the kustomization file is not provided, the builder }
// will try to retrieve the kustomization object from the k8s cluster.
// WithDryRun sets the dry-run flag, and needs to be provided if the builder is used for restMapper, err := rcg.ToRESTMapper()
// a dry-run. This flag works in conjunction with WithKustomizationFile, because the if err != nil {
// kustomization object is not retrieved from the k8s cluster when the dry-run flag is set. return nil, err
func NewBuilder(name, resources string, opts ...BuilderOptionFunc) (*Builder, error) { }
b := &Builder{ b := &Builder{
client: kubeClient,
restMapper: restMapper,
name: name, name: name,
namespace: *rcg.Namespace,
resourcesPath: resources, resourcesPath: resources,
} }
@@ -182,14 +147,6 @@ func NewBuilder(name, resources string, opts ...BuilderOptionFunc) (*Builder, er
b.timeout = defaultTimeout b.timeout = defaultTimeout
} }
if b.dryRun && b.kustomizationFile == "" {
return nil, fmt.Errorf("kustomization file is required for dry-run")
}
if !b.dryRun && b.client == nil {
return nil, fmt.Errorf("client is required for live run")
}
return b, nil return b, nil
} }
@@ -231,7 +188,7 @@ func (b *Builder) build() (m resmap.ResMap, err error) {
defer cancel() defer cancel()
// Get the kustomization object // Get the kustomization object
var k *kustomizev1.Kustomization k := &kustomizev1.Kustomization{}
if b.kustomizationFile != "" { if b.kustomizationFile != "" {
k, err = b.unMarshallKustomization() k, err = b.unMarshallKustomization()
if err != nil { if err != nil {
@@ -316,7 +273,7 @@ func (b *Builder) generate(kustomization kustomizev1.Kustomization, dirPath stri
if err != nil { if err != nil {
return "", err return "", err
} }
gen := kustomize.NewGenerator("", unstructured.Unstructured{Object: data}) gen := kustomize.NewGenerator(unstructured.Unstructured{Object: data})
// acuire the lock // acuire the lock
b.mu.Lock() b.mu.Lock()
@@ -326,13 +283,17 @@ func (b *Builder) generate(kustomization kustomizev1.Kustomization, dirPath stri
} }
func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomization, dirPath string) (resmap.ResMap, error) { func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomization, dirPath string) (resmap.ResMap, error) {
fs := filesys.MakeFsOnDisk() // TODO(hidde): provide option to enforce FS boundaries of local build
fs, err := filesys.MakeFsOnDiskSecureBuild("/")
if err != nil {
return nil, fmt.Errorf("kustomization build failed: %w", err)
}
// acuire the lock // acuire the lock
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
m, err := kustomize.Build(fs, dirPath) m, err := kustomize.BuildKustomization(fs, dirPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("kustomize build failed: %w", err) return nil, fmt.Errorf("kustomize build failed: %w", err)
} }
@@ -344,7 +305,7 @@ func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomizatio
if err != nil { if err != nil {
return nil, err return nil, err
} }
outRes, err := kustomize.SubstituteVariables(ctx, b.client, unstructured.Unstructured{Object: data}, res, b.dryRun) outRes, err := kustomize.SubstituteVariables(ctx, b.client, unstructured.Unstructured{Object: data}, res)
if err != nil { if err != nil {
return nil, fmt.Errorf("var substitution failed for '%s': %w", res.GetName(), err) return nil, fmt.Errorf("var substitution failed for '%s': %w", res.GetName(), err)
} }

View File

@@ -27,22 +27,20 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/fluxcd/flux2/pkg/printers"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/ssa"
"github.com/gonvenience/bunt" "github.com/gonvenience/bunt"
"github.com/gonvenience/ytbx" "github.com/gonvenience/ytbx"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-multierror"
"github.com/homeport/dyff/pkg/dyff" "github.com/homeport/dyff/pkg/dyff"
"github.com/lucasb-eyer/go-colorful" "github.com/lucasb-eyer/go-colorful"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/errors"
"sigs.k8s.io/cli-utils/pkg/kstatus/polling" "sigs.k8s.io/cli-utils/pkg/kstatus/polling"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/ssa"
"github.com/fluxcd/flux2/pkg/printers"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
) )
func (b *Builder) Manager() (*ssa.ResourceManager, error) { func (b *Builder) Manager() (*ssa.ResourceManager, error) {
@@ -87,7 +85,7 @@ func (b *Builder) Diff() (string, bool, error) {
} }
} }
var diffErrs []error var diffErrs error
// create an inventory of objects to be reconciled // create an inventory of objects to be reconciled
newInventory := newInventory() newInventory := newInventory()
for _, obj := range objects { for _, obj := range objects {
@@ -99,7 +97,7 @@ func (b *Builder) Diff() (string, bool, error) {
change, liveObject, mergedObject, err := resourceManager.Diff(ctx, obj, diffOptions) change, liveObject, mergedObject, err := resourceManager.Diff(ctx, obj, diffOptions)
if err != nil { if err != nil {
// gather errors and continue, as we want to see all the diffs // gather errors and continue, as we want to see all the diffs
diffErrs = append(diffErrs, err) diffErrs = multierror.Append(diffErrs, err)
continue continue
} }
@@ -137,7 +135,7 @@ func (b *Builder) Diff() (string, bool, error) {
b.spinner.Message("processing inventory") b.spinner.Message("processing inventory")
} }
if b.kustomization.Spec.Prune && len(diffErrs) == 0 { if b.kustomization.Spec.Prune && diffErrs == nil {
oldStatus := b.kustomization.Status.DeepCopy() oldStatus := b.kustomization.Status.DeepCopy()
if oldStatus.Inventory != nil { if oldStatus.Inventory != nil {
diffObjects, err := diffInventory(oldStatus.Inventory, newInventory) diffObjects, err := diffInventory(oldStatus.Inventory, newInventory)
@@ -157,7 +155,7 @@ func (b *Builder) Diff() (string, bool, error) {
} }
} }
return output.String(), createdOrDrifted, errors.Reduce(errors.Flatten(errors.NewAggregate(diffErrs))) return output.String(), createdOrDrifted, diffErrs
} }
func writeYamls(liveObject, mergedObject *unstructured.Unstructured) (string, string, string, error) { func writeYamls(liveObject, mergedObject *unstructured.Unstructured) (string, string, string, error) {

View File

@@ -1,63 +0,0 @@
/*
Copyright 2023 The Flux authors
Copyright 2018 The Kubernetes 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.
*/
// TODO: Remove this when
// https://github.com/kubernetes-sigs/controller-runtime/blob/c783d2527a7da76332a2d8d563a6ca0b80c12122/pkg/client/apiutil/apimachinery.go#L76-L104
// is included in a semver release.
package utils
import (
"errors"
"fmt"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)
// IsAPINamespaced returns true if the object is namespace scoped.
// For unstructured objects the gvk is found from the object itself.
func IsAPINamespaced(obj runtime.Object, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) {
gvk, err := apiutil.GVKForObject(obj, scheme)
if err != nil {
return false, err
}
return IsAPINamespacedWithGVK(gvk, scheme, restmapper)
}
// IsAPINamespacedWithGVK returns true if the object having the provided
// GVK is namespace scoped.
func IsAPINamespacedWithGVK(gk schema.GroupVersionKind, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) {
restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gk.Group, Kind: gk.Kind})
if err != nil {
return false, fmt.Errorf("failed to get restmapping: %w", err)
}
scope := restmapping.Scope.Name()
if scope == "" {
return false, errors.New("scope cannot be identified, empty scope returned")
}
if scope != apimeta.RESTScopeNameRoot {
return true, nil
}
return false, nil
}

View File

@@ -45,7 +45,7 @@ import (
imageautov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" imageautov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
imagereflectv1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" imagereflectv1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
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"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
runclient "github.com/fluxcd/pkg/runtime/client" runclient "github.com/fluxcd/pkg/runtime/client"
"github.com/fluxcd/pkg/version" "github.com/fluxcd/pkg/version"
@@ -54,6 +54,9 @@ import (
"github.com/fluxcd/flux2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen/install"
) )
type Utils struct {
}
type ExecMode string type ExecMode string
const ( const (

View File

@@ -1,8 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
resources: resources:
- https://github.com/fluxcd/helm-controller/releases/download/v0.29.0/helm-controller.crds.yaml - https://github.com/fluxcd/helm-controller/releases/download/v0.24.0/helm-controller.crds.yaml
- https://github.com/fluxcd/helm-controller/releases/download/v0.29.0/helm-controller.deployment.yaml - https://github.com/fluxcd/helm-controller/releases/download/v0.24.0/helm-controller.deployment.yaml
- account.yaml - account.yaml
transformers: transformers:
- labels.yaml - labels.yaml

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