1
0
mirror of synced 2026-03-01 19:26:55 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
fluxcdbot
86d0001421 Update toolkit components
- helm-controller to v0.17.2
  https://github.com/fluxcd/helm-controller/blob/v0.17.2/CHANGELOG.md
- notification-controller to v0.22.3
  https://github.com/fluxcd/notification-controller/blob/v0.22.3/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2022-03-16 03:18:39 +05:30
fluxcdbot
1ad9255b09 Update toolkit components
- image-automation-controller to v0.20.1
  https://github.com/fluxcd/image-automation-controller/blob/v0.20.1/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2022-03-01 21:21:03 +05:30
270 changed files with 2999 additions and 7735 deletions

View File

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

View File

@@ -8,8 +8,8 @@ pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
url="https://fluxcd.io/"
arch=("x86_64" "armv6h" "armv7h" "aarch64")
license=("APACHE")
optdepends=('bash-completion: auto-completion for flux in Bash'
'zsh-completions: auto-completion for flux in ZSH')
optdepends=('bash-completion: auto-completion for flux in Bash',
'zsh-completions: auto-completion for flux in ZSH')
source_x86_64=(
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_amd64.tar.gz"
)

View File

@@ -1,7 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -18,11 +18,11 @@
set -eu
KIND_VERSION=0.14.0
KUBECTL_VERSION=1.24.0
KUSTOMIZE_VERSION=4.5.4
HELM_VERSION=3.8.2
GITHUB_RUNNER_VERSION=2.291.1
KIND_VERSION=0.11.1
KUBECTL_VERSION=1.21.2
KUSTOMIZE_VERSION=4.1.3
HELM_VERSION=3.7.2
GITHUB_RUNNER_VERSION=2.285.1
PACKAGES="apt-transport-https ca-certificates software-properties-common build-essential libssl-dev gnupg lsb-release jq pkg-config"
# install prerequisites

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,38 +1,34 @@
name: e2e-bootstrap
name: bootstrap
on:
workflow_dispatch:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
contents: read
jobs:
e2e-boostrap-github:
github:
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:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Restore Go cache
uses: actions/cache@v3
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go1.17-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go1.18-
${{ runner.os }}-go1.17-
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v2
with:
go-version: 1.19.x
go-version: 1.17.x
- name: Setup Kubernetes
uses: engineerd/setup-kind@v0.5.0
with:
version: v0.16.0
image: kindest/node:v1.25.2@sha256:9be91e9e9cdf116809841fc77ebdb8845443c4c72fe5218f3ae9eb57fdb4bace
version: v0.11.1
image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6
- name: Setup Kustomize
uses: fluxcd/pkg//actions/kustomize@main
- name: Build

View File

@@ -3,23 +3,20 @@ name: e2e-arm64
on:
workflow_dispatch:
push:
branches: [ main, update-components ]
permissions:
contents: read
branches: [ main, update-components, equinix-runners ]
jobs:
e2e-arm64-kubernetes:
test:
# Hosted on Equinix
# Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners
runs-on: [self-hosted, Linux, ARM64, equinix]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v2
with:
go-version: 1.19.x
go-version: 1.17.x
- name: Prepare
id: prep
run: |

View File

@@ -7,31 +7,31 @@ on:
push:
branches: [ azure* ]
permissions:
contents: read
jobs:
e2e-amd64-aks:
runs-on: ubuntu-22.04
e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Restore Go cache
uses: actions/cache@v3
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go1.17-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go1.18-
${{ runner.os }}-go1.17-
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.19.x
go-version: 1.17.x
- name: Install libgit2
run: |
echo "deb http://archive.ubuntu.com/ubuntu/ kinetic universe" | sudo tee -a /etc/apt/sources.list
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 -t kinetic libgit2-dev=1.3.0+dfsg.1-3ubuntu1
sudo apt-get install -y --allow-downgrades libgit2-dev/unstable zlib1g-dev/unstable libssh2-1-dev/unstable libpcre3-dev/unstable
- name: Setup Flux CLI
run: |
make build
@@ -44,9 +44,9 @@ jobs:
mkdir -p $HOME/.local/bin
mv sops-v3.7.1.linux $HOME/.local/bin/sops
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.2
uses: hashicorp/setup-terraform@v1
with:
terraform_version: 1.2.8
terraform_version: 1.0.7
terraform_wrapper: false
- name: Setup Azure CLI
run: |

View File

@@ -1,37 +1,28 @@
name: e2e
on:
workflow_dispatch:
push:
branches: [ main ]
branches: [ main, e2e* ]
pull_request:
branches: [ main, oci ]
permissions:
contents: read
branches: [ main ]
jobs:
e2e-amd64-kubernetes:
kind:
runs-on: ubuntu-latest
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Restore Go cache
uses: actions/cache@v3
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.18-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go1.17-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go1.18-
${{ runner.os }}-go1.17-
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v2
with:
go-version: 1.19.x
go-version: 1.17.x
- name: Setup Kubernetes
uses: engineerd/setup-kind@v0.5.0
with:
@@ -177,36 +168,6 @@ jobs:
- name: flux delete source git
run: |
/tmp/flux delete source git podinfo --silent
- name: flux oci artifacts
run: |
/tmp/flux push artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
--path="./manifests" \
--source="${{ github.repositoryUrl }}" \
--revision="${{ github.ref }}/${{ github.sha }}"
/tmp/flux tag artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
--tag latest
/tmp/flux list artifacts oci://localhost:5000/fluxcd/flux
- name: flux oci repositories
run: |
/tmp/flux create source oci podinfo-oci \
--url oci://ghcr.io/stefanprodan/manifests/podinfo \
--tag-semver 6.1.x \
--interval 10m
/tmp/flux create kustomization podinfo-oci \
--source=OCIRepository/podinfo-oci \
--path="./kustomize" \
--prune=true \
--interval=5m \
--target-namespace=default \
--wait=true \
--health-check-timeout=3m
/tmp/flux reconcile source oci podinfo-oci
/tmp/flux suspend source oci podinfo-oci
/tmp/flux get sources oci
/tmp/flux resume source oci podinfo-oci
/tmp/flux export source oci podinfo-oci
/tmp/flux delete ks podinfo-oci --silent
/tmp/flux delete source oci podinfo-oci --silent
- name: flux create tenant
run: |
/tmp/flux create tenant dev-team --with-namespace=apps

21
.github/workflows/rebase.yaml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: rebase
on:
pull_request:
types: [ opened ]
issue_comment:
types: [ created ]
jobs:
rebase:
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase') && (github.event.comment.author_association == 'CONTRIBUTOR' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER')
runs-on: ubuntu-latest
steps:
- name: Checkout the latest code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.3.1
env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}

View File

@@ -5,29 +5,27 @@ on:
tags: [ 'v*' ]
permissions:
contents: read
contents: write # needed to write releases
id-token: write # needed for keyless signing
packages: write # needed for ghcr access
jobs:
release-flux-cli:
goreleaser:
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:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Unshallow
run: git fetch --prune --unshallow
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v2
with:
go-version: 1.19.x
go-version: 1.17.x
- name: Setup QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v1
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v1
- name: Setup Syft
uses: anchore/sbom-action/download-syft@v0
- name: Setup Cosign
@@ -35,13 +33,13 @@ jobs:
- name: Setup Kustomize
uses: fluxcd/pkg//actions/kustomize@main
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v1
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v1
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
@@ -75,7 +73,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
uses: goreleaser/goreleaser-action@v1
with:
version: latest
args: release --release-notes=output/notes.md --skip-validate
@@ -83,69 +81,3 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
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@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

@@ -1,7 +1,6 @@
name: scan
name: Scan
on:
workflow_dispatch:
push:
branches: [ main ]
pull_request:
@@ -9,15 +8,12 @@ on:
schedule:
- cron: '18 10 * * 3'
permissions:
contents: read
jobs:
scan-fossa:
fossa:
name: FOSSA
runs-on: ubuntu-latest
if: github.actor != 'dependabot[bot]'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@v1
with:
@@ -25,13 +21,12 @@ jobs:
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
github-token: ${{ github.token }}
scan-snyk:
snyk:
name: Snyk
runs-on: ubuntu-latest
permissions:
security-events: write
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]'
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Setup Kustomize
uses: fluxcd/pkg//actions/kustomize@main
- name: Build manifests
@@ -45,27 +40,21 @@ jobs:
with:
args: --sarif-file-output=snyk.sarif
- name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v2
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: snyk.sarif
scan-codeql:
codeql:
name: CodeQL
runs-on: ubuntu-latest
permissions:
security-events: write
if: github.actor != 'dependabot[bot]'
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.19.x
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v1
with:
languages: go
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v1

View File

@@ -1,4 +1,4 @@
name: update
name: Update Components
on:
workflow_dispatch:
@@ -7,22 +7,16 @@ on:
push:
branches: [main]
permissions:
contents: read
jobs:
update-components:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v2
with:
go-version: 1.19.x
go-version: 1.17.x
- name: Update component versions
id: update
run: |
@@ -48,7 +42,8 @@ jobs:
if [[ "${MOD_VERSION}" != "${LATEST_VERSION}" ]]; then
go mod edit -require="github.com/fluxcd/$1/api@${LATEST_VERSION}"
make tidy
rm go.sum
go mod tidy
changed=true
fi
@@ -75,7 +70,7 @@ jobs:
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v4
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
commit-message: |

View File

@@ -59,7 +59,7 @@ This project is composed of:
### Understanding the code
To get started with developing controllers, you might want to review
[our guide](https://fluxcd.io/flux/gitops-toolkit/source-watcher/) which
[our guide](https://fluxcd.io/docs/gitops-toolkit/source-watcher/) which
walks you through writing a short and concise controller that watches out
for source changes.
@@ -67,10 +67,9 @@ for source changes.
Prerequisites:
* go >= 1.19
* go >= 1.17
* kubectl >= 1.20
* kustomize >= 4.4
* coreutils (on Mac OS)
Install the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with:
@@ -97,25 +96,6 @@ Then you can run the end-to-end tests with:
make e2e
```
When the output of the Flux CLI changes, to automatically update the golden
files used in the test, pass `-update` flag to the test as:
```bash
make e2e TEST_ARGS="-update"
```
Since not all packages use golden files for testing, `-update` argument must be
passed only for the packages that use golden files. Use the variables
`TEST_PKG_PATH` for unit tests and `E2E_TEST_PKG_PATH` for e2e tests, to set the
path of the target test package:
```bash
# Unit test
make test TEST_PKG_PATH="./cmd/flux" TEST_ARGS="-update"
# e2e test
make e2e E2E_TEST_PKG_PATH="./cmd/flux" TEST_ARGS="-update"
```
Teardown the e2e environment with:
```bash

View File

@@ -1,15 +1,15 @@
FROM alpine:3.16 as builder
FROM alpine:3.15 as builder
RUN apk add --no-cache ca-certificates curl
ARG ARCH=linux/amd64
ARG KUBECTL_VER=1.25.3
ARG KUBECTL_VER=1.23.1
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 && \
kubectl version --client=true
FROM alpine:3.16 as flux-cli
FROM alpine:3.15 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

View File

@@ -2,7 +2,19 @@ The maintainers are generally available in Slack at
https://cloud-native.slack.com in #flux (https://cloud-native.slack.com/messages/CLAJ40HV3)
(obtain an invitation at https://slack.cncf.io/).
The Flux2 maintainers team is identical with the core maintainers of the project
as listed in
These maintainers are shared with other Flux v2-related git
repositories under https://github.com/fluxcd, as noted in their
respective MAINTAINERS files.
https://github.com/fluxcd/community/blob/main/CORE-MAINTAINERS
For convenience, they are reflected in the GitHub team
@fluxcd/flux2-maintainers -- if the list here changes, that team also
should.
In alphabetical order:
Aurel Canciu, NexHealth <aurel.canciu@nexhealth.com> (github: @relu, slack: relu)
Hidde Beydals, Weaveworks <hidde@weave.works> (github: @hiddeco, slack: hidde)
Max Jonas Werner, D2iQ <max@e13.dev> (github: @makkes, slack: max)
Philip Laine, Xenit <philip.laine@xenit.se> (github: @phillebaba, slack: phillebaba)
Stefan Prodan, Weaveworks <stefan@weave.works> (github: @stefanprodan, slack: stefanprodan)
Sunny, Weaveworks <sunny@weave.works> (github: @darkowlzz, slack: darkowlzz)

View File

@@ -1,5 +1,4 @@
VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | head -n 1 | tr -d '"')
DEV_VERSION?=0.0.0-$(shell git rev-parse --abbrev-ref HEAD)-$(shell git rev-parse --short HEAD)-$(shell date +%s)
EMBEDDED_MANIFESTS_TARGET=cmd/flux/.manifests.done
TEST_KUBECONFIG?=/tmp/flux-e2e-test-kubeconfig
# Architecture to use envtest with
@@ -17,8 +16,8 @@ rwildcard=$(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2
all: test build
tidy:
go mod tidy -compat=1.19
cd tests/azure && go mod tidy -compat=1.19
go mod tidy
cd tests/azure && go mod tidy
fmt:
go fmt ./...
@@ -36,13 +35,11 @@ cleanup-kind:
rm $(TEST_KUBECONFIG)
KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)"
TEST_PKG_PATH="./..."
test: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet install-envtest
KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test $(TEST_PKG_PATH) -coverprofile cover.out --tags=unit $(TEST_ARGS)
KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test ./... -coverprofile cover.out --tags=unit
E2E_TEST_PKG_PATH="./cmd/flux/..."
e2e: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet
TEST_KUBECONFIG=$(TEST_KUBECONFIG) go test $(E2E_TEST_PKG_PATH) -coverprofile e2e.cover.out --tags=e2e -v -failfast $(TEST_ARGS)
TEST_KUBECONFIG=$(TEST_KUBECONFIG) go test ./cmd/flux/... -coverprofile e2e.cover.out --tags=e2e -v -failfast
test-with-kind: install-envtest
make setup-kind
@@ -56,9 +53,6 @@ $(EMBEDDED_MANIFESTS_TARGET): $(call rwildcard,manifests/,*.yaml *.json)
build: $(EMBEDDED_MANIFESTS_TARGET)
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VERSION=$(VERSION)" -o ./bin/flux ./cmd/flux
build-dev: $(EMBEDDED_MANIFESTS_TARGET)
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VERSION=$(DEV_VERSION)" -o ./bin/flux ./cmd/flux
.PHONY: install
install:
CGO_ENABLED=0 go install ./cmd/flux

View File

@@ -1,13 +1,14 @@
# 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)
[![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)
[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/flux2)](https://artifacthub.io/packages/helm/fluxcd-community/flux2)
[![e2e](https://github.com/fluxcd/flux2/workflows/e2e/badge.svg)](https://github.com/fluxcd/flux2/actions)
[![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
configuration (like Git repositories and OCI artifacts),
and automating updates to configuration when there is new code to deploy.
configuration (like Git repositories), and automating updates to
configuration when there is new code to deploy.
Flux version 2 ("v2") is built from the ground up to use Kubernetes'
API extension system, and to integrate with Prometheus and other core
@@ -19,19 +20,18 @@ Flux v2 is constructed with the [GitOps Toolkit](#gitops-toolkit), a
set of composable APIs and specialized tools for building Continuous
Delivery on top of Kubernetes.
Flux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) project, used in
production by various [organisations](https://fluxcd.io/adopters) and [cloud providers](https://fluxcd.io/ecosystem).
Flux is a Cloud Native Computing Foundation ([CNCF](https://www.cncf.io/)) project.
## Quickstart and documentation
To get started check out this [guide](https://fluxcd.io/flux/get-started/)
To get started check out this [guide](https://fluxcd.io/docs/get-started/)
on how to bootstrap Flux on Kubernetes and deploy a sample application in a GitOps manner.
For more comprehensive documentation, see the following guides:
- [Ways of structuring your repositories](https://fluxcd.io/flux/guides/repository-structure/)
- [Manage Helm Releases](https://fluxcd.io/flux/guides/helmreleases/)
- [Automate image updates to Git](https://fluxcd.io/flux/guides/image-update/)
- [Manage Kubernetes secrets with Mozilla SOPS](https://fluxcd.io/flux/guides/mozilla-sops/)
- [Ways of structuring your repositories](https://fluxcd.io/docs/guides/repository-structure/)
- [Manage Helm Releases](https://fluxcd.io/docs/guides/helmreleases/)
- [Automate image updates to Git](https://fluxcd.io/docs/guides/image-update/)
- [Manage Kubernetes secrets with Mozilla SOPS](https://fluxcd.io/docs/guides/mozilla-sops/)
If you need help, please refer to our **[Support page](https://fluxcd.io/support/)**.
@@ -46,28 +46,27 @@ automation tooling.
You can use the toolkit to extend Flux, or to build your own systems
for continuous delivery -- see [the developer
guides](https://fluxcd.io/flux/gitops-toolkit/source-watcher/).
guides](https://fluxcd.io/docs/gitops-toolkit/source-watcher/).
### Components
- [Source Controller](https://fluxcd.io/flux/components/source/)
- [GitRepository CRD](https://fluxcd.io/flux/components/source/gitrepositories/)
- [OCIRepository CRD](https://fluxcd.io/flux/components/source/ocirepositories/)
- [HelmRepository CRD](https://fluxcd.io/flux/components/source/helmrepositories/)
- [HelmChart CRD](https://fluxcd.io/flux/components/source/helmcharts/)
- [Bucket CRD](https://fluxcd.io/flux/components/source/buckets/)
- [Kustomize Controller](https://fluxcd.io/flux/components/kustomize/)
- [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomization/)
- [Helm Controller](https://fluxcd.io/flux/components/helm/)
- [HelmRelease CRD](https://fluxcd.io/flux/components/helm/helmreleases/)
- [Notification Controller](https://fluxcd.io/flux/components/notification/)
- [Provider CRD](https://fluxcd.io/flux/components/notification/provider/)
- [Alert CRD](https://fluxcd.io/flux/components/notification/alert/)
- [Receiver CRD](https://fluxcd.io/flux/components/notification/receiver/)
- [Image Automation Controllers](https://fluxcd.io/flux/components/image/)
- [ImageRepository CRD](https://fluxcd.io/flux/components/image/imagerepositories/)
- [ImagePolicy CRD](https://fluxcd.io/flux/components/image/imagepolicies/)
- [ImageUpdateAutomation CRD](https://fluxcd.io/flux/components/image/imageupdateautomations/)
- [Source Controller](https://fluxcd.io/docs/components/source/)
- [GitRepository CRD](https://fluxcd.io/docs/components/source/gitrepositories/)
- [HelmRepository CRD](https://fluxcd.io/docs/components/source/helmrepositories/)
- [HelmChart CRD](https://fluxcd.io/docs/components/source/helmcharts/)
- [Bucket CRD](https://fluxcd.io/docs/components/source/buckets/)
- [Kustomize Controller](https://fluxcd.io/docs/components/kustomize/)
- [Kustomization CRD](https://fluxcd.io/docs/components/kustomize/kustomization/)
- [Helm Controller](https://fluxcd.io/docs/components/helm/)
- [HelmRelease CRD](https://fluxcd.io/docs/components/helm/helmreleases/)
- [Notification Controller](https://fluxcd.io/docs/components/notification/)
- [Provider CRD](https://fluxcd.io/docs/components/notification/provider/)
- [Alert CRD](https://fluxcd.io/docs/components/notification/alert/)
- [Receiver CRD](https://fluxcd.io/docs/components/notification/receiver/)
- [Image Automation Controllers](https://fluxcd.io/docs/components/image/)
- [ImageRepository CRD](https://fluxcd.io/docs/components/image/imagerepositories/)
- [ImagePolicy CRD](https://fluxcd.io/docs/components/image/imagepolicies/)
- [ImageUpdateAutomation CRD](https://fluxcd.io/docs/components/image/imageupdateautomations/)
## Community
@@ -75,19 +74,18 @@ Need help or want to contribute? Please see the links below. The Flux project is
new contributors and there are a multitude of ways to get involved.
- 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/docs/get-started/) and give us feedback
- Need help?
- First: Ask questions on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions).
- Second: Talk to us in the #flux channel on [CNCF Slack](https://slack.cncf.io/).
- First: Ask questions on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions)
- Second: Talk to us in the #flux channel on [CNCF Slack](https://slack.cncf.io/)
- Please follow our [Support Guidelines](https://fluxcd.io/support/)
(in short: be nice, be respectful of volunteers' time, understand that maintainers and
contributors cannot respond to all DMs, and keep discussions in the public #flux channel as much as possible).
- Have feature proposals or want to contribute?
- Propose features on our [GitHub 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)).
- 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 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 the [project roadmap](https://fluxcd.io/roadmap/).
- Check out [how to contribute](CONTRIBUTING.md) to the project
### Events

View File

@@ -32,7 +32,7 @@ You can download a specific version with:
- name: Setup Flux CLI
uses: fluxcd/flux2/action@main
with:
version: 0.32.0
version: 0.8.0
```
### Automate Flux updates
@@ -74,92 +74,6 @@ jobs:
${{ steps.update.outputs.flux_version }}
```
### Push Kubernetes manifests to container registries
Example workflow for publishing Kubernetes manifests bundled as OCI artifacts to GitHub Container Registry:
```yaml
name: push-artifact-staging
on:
push:
branches:
- 'main'
permissions:
packages: write # needed for ghcr.io access
env:
OCI_REPO: "oci://ghcr.io/my-org/manifests/${{ github.event.repository.name }}"
jobs:
kubernetes:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Flux CLI
uses: fluxcd/flux2/action@main
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate manifests
run: |
kustomize build ./manifests/staging > ./deploy/app.yaml
- name: Push manifests
run: |
flux push artifact $OCI_REPO:$(git rev-parse --short HEAD) \
--path="./deploy" \
--source="$(git config --get remote.origin.url)" \
--revision="$(git branch --show-current)/$(git rev-parse HEAD)"
- name: Deploy manifests to staging
run: |
flux tag artifact $OCI_REPO:$(git rev-parse --short HEAD) --tag staging
```
Example workflow for publishing Kubernetes manifests bundled as OCI artifacts to Docker Hub:
```yaml
name: push-artifact-production
on:
push:
tags:
- '*'
env:
OCI_REPO: "oci://docker.io/my-org/app-config"
jobs:
kubernetes:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Flux CLI
uses: fluxcd/flux2/action@main
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Generate manifests
run: |
kustomize build ./manifests/production > ./deploy/app.yaml
- name: Push manifests
run: |
flux push artifact $OCI_REPO:$(git tag --points-at HEAD) \
--path="./deploy" \
--source="$(git config --get remote.origin.url)" \
--revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)"
- name: Deploy manifests to production
run: |
flux tag artifact $OCI_REPO:$(git tag --points-at HEAD) --tag production
```
### End-to-end testing
Example workflow for running Flux in Kubernetes Kind:

View File

@@ -19,13 +19,13 @@ package main
import (
"crypto/elliptic"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
)
@@ -88,7 +88,7 @@ func init() {
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, "components", rootArgs.defaults.Components,
"list of components, accepts comma-separated values")
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil,
"list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller'")
"list of components in addition to those supplied or defaulted, accepts comma-separated values")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd",
"container registry where the toolkit images are published")
@@ -154,7 +154,7 @@ func buildEmbeddedManifestBase() (string, error) {
if !isEmbeddedVersion(bootstrapArgs.version) {
return "", nil
}
tmpBaseDir, err := manifestgen.MkdirTempAbs("", "flux-manifests-")
tmpBaseDir, err := os.MkdirTemp("", "flux-manifests-")
if err != nil {
return "", err
}

View File

@@ -25,12 +25,11 @@ import (
"github.com/go-git/go-git/v5/plumbing/transport/http"
"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/utils"
"github.com/fluxcd/flux2/pkg/bootstrap"
"github.com/fluxcd/flux2/pkg/bootstrap/git/gogit"
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
"github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
@@ -122,7 +121,7 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}
@@ -166,7 +165,7 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
}
// Lazy go-git repository
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
tmpDir, err := os.MkdirTemp("", "flux-bootstrap-")
if err != nil {
return fmt.Errorf("failed to create temporary working dir: %w", err)
}
@@ -252,7 +251,7 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithProviderTeamPermissions(mapTeamSlice(bServerArgs.teams, bServerDefaultPermission)),
bootstrap.WithReadWriteKeyPermissions(bServerArgs.readWriteKey),
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
bootstrap.WithKubeconfig(kubeconfigArgs),
bootstrap.WithLogger(logger),
bootstrap.WithCABundle(caBundle),
bootstrap.WithGitCommitSigning(bootstrapArgs.gpgKeyRingPath, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),

View File

@@ -31,11 +31,10 @@ import (
"github.com/spf13/cobra"
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/utils"
"github.com/fluxcd/flux2/pkg/bootstrap"
"github.com/fluxcd/flux2/pkg/bootstrap/git/gogit"
"github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
@@ -54,9 +53,6 @@ command will perform an upgrade if needed.`,
# Run bootstrap for a Git repository and authenticate using a password
flux bootstrap git --url=https://example.com/repository.git --password=<password>
# Run bootstrap for a Git repository and authenticate using a password from environment variable
GIT_PASSWORD=<password> && flux bootstrap git --url=https://example.com/repository.git
# Run bootstrap for a Git repository with a passwordless private key
flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key>
@@ -67,19 +63,14 @@ command will perform an upgrade if needed.`,
}
type gitFlags struct {
url string
interval time.Duration
path flags.SafeRelativePath
username string
password string
silent bool
insecureHttpAllowed bool
url string
interval time.Duration
path flags.SafeRelativePath
username string
password string
silent bool
}
const (
gitPasswordEnvVar = "GIT_PASSWORD"
)
var gitArgs gitFlags
func init() {
@@ -89,25 +80,11 @@ func init() {
bootstrapGitCmd.Flags().StringVarP(&gitArgs.username, "username", "u", "git", "basic authentication username")
bootstrapGitCmd.Flags().StringVarP(&gitArgs.password, "password", "p", "", "basic authentication password")
bootstrapGitCmd.Flags().BoolVarP(&gitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
bootstrapGitCmd.Flags().BoolVar(&gitArgs.insecureHttpAllowed, "allow-insecure-http", false, "allows http git url connections")
bootstrapCmd.AddCommand(bootstrapGitCmd)
}
func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
gitPassword := os.Getenv(gitPasswordEnvVar)
if gitPassword != "" && gitArgs.password == "" {
gitArgs.password = gitPassword
}
if bootstrapArgs.tokenAuth && gitArgs.password == "" {
var err error
gitPassword, err = readPasswordFromStdin("Please enter your Git repository password: ")
if err != nil {
return fmt.Errorf("could not read token: %w", err)
}
gitArgs.password = gitPassword
}
if err := bootstrapValidate(); err != nil {
return err
}
@@ -124,7 +101,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}
@@ -140,7 +117,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
defer os.RemoveAll(manifestsBase)
// Lazy go-git repository
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
tmpDir, err := os.MkdirTemp("", "flux-bootstrap-")
if err != nil {
return fmt.Errorf("failed to create temporary working dir: %w", err)
}
@@ -192,9 +169,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
// Configure repository URL to match auth config for sync.
repositoryURL.User = nil
if !gitArgs.insecureHttpAllowed {
repositoryURL.Scheme = "https"
}
repositoryURL.Scheme = "https"
} else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.Password = gitArgs.password
@@ -250,7 +225,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
bootstrap.WithKubeconfig(kubeconfigArgs),
bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
bootstrap.WithLogger(logger),
bootstrap.WithCABundle(caBundle),
@@ -273,14 +248,6 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
// SSH-agent is attempted.
func transportForURL(u *url.URL) (transport.AuthMethod, error) {
switch u.Scheme {
case "http":
if !gitArgs.insecureHttpAllowed {
return nil, fmt.Errorf("scheme http is insecure, pass --allow-insecure-http=true to allow it")
}
return &http.BasicAuth{
Username: gitArgs.username,
Password: gitArgs.password,
}, nil
case "https":
return &http.BasicAuth{
Username: gitArgs.username,

View File

@@ -25,12 +25,11 @@ import (
"github.com/go-git/go-git/v5/plumbing/transport/http"
"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/utils"
"github.com/fluxcd/flux2/pkg/bootstrap"
"github.com/fluxcd/flux2/pkg/bootstrap/git/gogit"
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
"github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
@@ -126,7 +125,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}
@@ -162,7 +161,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
}
// Lazy go-git repository
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
tmpDir, err := os.MkdirTemp("", "flux-bootstrap-")
if err != nil {
return fmt.Errorf("failed to create temporary working dir: %w", err)
}
@@ -241,7 +240,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithProviderTeamPermissions(mapTeamSlice(githubArgs.teams, ghDefaultPermission)),
bootstrap.WithReadWriteKeyPermissions(githubArgs.readWriteKey),
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
bootstrap.WithKubeconfig(kubeconfigArgs),
bootstrap.WithLogger(logger),
bootstrap.WithCABundle(caBundle),
bootstrap.WithGitCommitSigning(bootstrapArgs.gpgKeyRingPath, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),

View File

@@ -27,12 +27,11 @@ import (
"github.com/go-git/go-git/v5/plumbing/transport/http"
"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/utils"
"github.com/fluxcd/flux2/pkg/bootstrap"
"github.com/fluxcd/flux2/pkg/bootstrap/git/gogit"
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
"github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
@@ -130,7 +129,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}
@@ -173,7 +172,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
}
// Lazy go-git repository
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
tmpDir, err := os.MkdirTemp("", "flux-bootstrap-")
if err != nil {
return fmt.Errorf("failed to create temporary working dir: %w", err)
}
@@ -255,7 +254,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithProviderTeamPermissions(mapTeamSlice(gitlabArgs.teams, glDefaultPermission)),
bootstrap.WithReadWriteKeyPermissions(gitlabArgs.readWriteKey),
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
bootstrap.WithKubeconfig(kubeconfigArgs),
bootstrap.WithLogger(logger),
bootstrap.WithCABundle(caBundle),
bootstrap.WithGitCommitSigning(bootstrapArgs.gpgKeyRingPath, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),

View File

@@ -1,83 +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 (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
oci "github.com/fluxcd/pkg/oci/client"
"github.com/fluxcd/pkg/sourceignore"
)
var buildArtifactCmd = &cobra.Command{
Use: "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.`,
Example: ` # Build the given manifests directory into an artifact
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
tar -ztvf ./path/to/artifact.tgz
`,
RunE: buildArtifactCmdRun,
}
type buildArtifactFlags struct {
output string
path string
ignorePaths []string
}
var excludeOCI = append(strings.Split(sourceignore.ExcludeVCS, ","), strings.Split(sourceignore.ExcludeExt, ",")...)
var buildArtifactArgs buildArtifactFlags
func init() {
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().StringSliceVar(&buildArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
buildCmd.AddCommand(buildArtifactCmd)
}
func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
if buildArtifactArgs.path == "" {
return fmt.Errorf("invalid path %q", buildArtifactArgs.path)
}
if _, err := os.Stat(buildArtifactArgs.path); err != nil {
return fmt.Errorf("invalid path '%s', must point to an existing directory or file", buildArtifactArgs.path)
}
logger.Actionf("building artifact from %s", buildArtifactArgs.path)
ociClient := oci.NewLocalClient()
if err := ociClient.Build(buildArtifactArgs.output, buildArtifactArgs.path, buildArtifactArgs.ignorePaths); err != nil {
return fmt.Errorf("bulding artifact failed, error: %w", err)
}
logger.Successf("artifact created at %s", buildArtifactArgs.output)
return nil
}

View File

@@ -33,28 +33,21 @@ var buildKsCmd = &cobra.Command{
Short: "Build Kustomization",
Long: `The build command queries the Kubernetes API and fetches the specified Flux Kustomization.
It then uses the fetched in cluster flux kustomization to perform needed transformation on the local kustomization.yaml
pointed at by --path. The local kustomization.yaml is generated if it does not exist. Finally it builds the overlays using the local kustomization.yaml, and write the resulting multi-doc YAML to stdout.
It is possible to specify a Flux kustomization file using --kustomization-file.`,
pointed at by --path. The local kustomization.yaml is generated if it does not exist. Finally it builds the overlays using the local kustomization.yaml, and write the resulting multi-doc YAML to stdout.`,
Example: `# Build the local manifests as they were built on the cluster
flux build kustomization my-app --path ./path/to/local/manifests
# 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`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: buildKsCmdRun,
}
type buildKsFlags struct {
kustomizationFile string
path string
path string
}
var buildKsArgs buildKsFlags
func init() {
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.path, "path", "", "Path to the manifests location.)")
buildCmd.AddCommand(buildKsCmd)
}
@@ -72,13 +65,7 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
}
if buildKsArgs.kustomizationFile != "" {
if fs, err := os.Stat(buildKsArgs.kustomizationFile); os.IsNotExist(err) || fs.IsDir() {
return fmt.Errorf("invalid kustomization file %q", buildKsArgs.kustomizationFile)
}
}
builder, err := build.NewBuilder(kubeconfigArgs, kubeclientOptions, name, buildKsArgs.path, build.WithTimeout(rootArgs.timeout), build.WithKustomizationFile(buildKsArgs.kustomizationFile))
builder, err := build.NewBuilder(kubeconfigArgs, name, buildKsArgs.path, build.WithTimeout(rootArgs.timeout))
if err != nil {
return err
}

View File

@@ -20,10 +20,7 @@ limitations under the License.
package main
import (
"bytes"
"os"
"testing"
"text/template"
)
func setup(t *testing.T, tmpl map[string]string) {
@@ -57,12 +54,6 @@ func TestBuildKustomization(t *testing.T) {
resultFile: "./testdata/build-kustomization/podinfo-without-service-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build deployment and configmap with var substitution",
args: "build kustomization podinfo --path ./testdata/build-kustomization/var-substitution",
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
}
tmpl := map[string]string{
@@ -90,101 +81,3 @@ func TestBuildKustomization(t *testing.T) {
})
}
}
func TestBuildLocalKustomization(t *testing.T) {
podinfo := `apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: podinfo
namespace: {{ .fluxns }}
spec:
interval: 5m0s
path: ./kustomize
force: true
prune: true
sourceRef:
kind: GitRepository
name: podinfo
targetNamespace: default
postBuild:
substitute:
cluster_env: "prod"
cluster_region: "eu-central-1"
`
tests := []struct {
name string
args string
resultFile string
assertFunc string
}{
{
name: "no args",
args: "build kustomization podinfo --kustomization-file ./wrongfile/ --path ./testdata/build-kustomization/podinfo",
resultFile: "invalid kustomization file \"./wrongfile/\"",
assertFunc: "assertError",
},
{
name: "build podinfo",
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/podinfo",
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build podinfo without service",
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/delete-service",
resultFile: "./testdata/build-kustomization/podinfo-without-service-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build deployment and configmap with var substitution",
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/var-substitution",
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
}
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-source.yaml", tmpl, t)
temp, err := template.New("podinfo").Parse(podinfo)
if err != nil {
t.Fatal(err)
}
var b bytes.Buffer
err = temp.Execute(&b, tmpl)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile("./testdata/build-kustomization/podinfo.yaml", b.Bytes(), 0666)
if err != nil {
t.Fatal(err)
}
defer os.Remove("./testdata/build-kustomization/podinfo.yaml")
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var assert assertFunc
switch tt.assertFunc {
case "assertGoldenTemplateFile":
assert = assertGoldenTemplateFile(tt.resultFile, tmpl)
case "assertError":
assert = assertError(tt.resultFile)
}
cmd := cmdTestCase{
args: tt.args + " -n " + tmpl["fluxns"],
assert: assert,
}
cmd.runTestCmd(t)
})
}
}

View File

@@ -24,7 +24,6 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/spf13/cobra"
v1 "k8s.io/api/apps/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -96,17 +95,9 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
if !componentsCheck() {
checkFailed = true
}
logger.Actionf("checking crds")
if !crdsCheck() {
checkFailed = true
}
if checkFailed {
logger.Failuref("check failed")
os.Exit(1)
}
logger.Successf("all checks passed")
return nil
}
@@ -134,7 +125,7 @@ func fluxCheck() {
}
func kubernetesCheck(constraints []string) bool {
cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
cfg, err := utils.KubeConfig(kubeconfigArgs)
if err != nil {
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
return false
@@ -182,7 +173,7 @@ func componentsCheck() bool {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeConfig, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
kubeConfig, err := utils.KubeConfig(kubeconfigArgs)
if err != nil {
return false
}
@@ -192,7 +183,7 @@ func componentsCheck() bool {
return false
}
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return false
}
@@ -200,14 +191,7 @@ func componentsCheck() bool {
ok := true
selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}
var list v1.DeploymentList
ns := *kubeconfigArgs.Namespace
if err := kubeClient.List(ctx, &list, client.InNamespace(ns), selector); err == nil {
if len(list.Items) == 0 {
logger.Failuref("no controllers found in the '%s' namespace with the label selector '%s=%s'",
ns, manifestgen.PartOfLabelKey, manifestgen.PartOfLabelValue)
return false
}
if err := kubeClient.List(ctx, &list, client.InNamespace(*kubeconfigArgs.Namespace), selector); err == nil {
for _, d := range list.Items {
if ref, err := buildComponentObjectRefs(d.Name); err == nil {
if err := statusChecker.Assess(ref...); err != nil {
@@ -221,34 +205,3 @@ func componentsCheck() bool {
}
return ok
}
func crdsCheck() bool {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return false
}
ok := true
selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}
var list apiextensionsv1.CustomResourceDefinitionList
if err := kubeClient.List(ctx, &list, client.InNamespace(*kubeconfigArgs.Namespace), selector); err == nil {
if len(list.Items) == 0 {
logger.Failuref("no crds found with the label selector '%s=%s'",
manifestgen.PartOfLabelKey, manifestgen.PartOfLabelValue)
return false
}
for _, crd := range list.Items {
if len(crd.Status.StoredVersions) > 0 {
logger.Successf(crd.Name + "/" + crd.Status.StoredVersions[0])
} else {
ok = false
logger.Failuref("no stored versions for %s", crd.Name)
}
}
}
return ok
}

View File

@@ -26,6 +26,7 @@ import (
"testing"
"github.com/fluxcd/flux2/internal/utils"
"k8s.io/apimachinery/pkg/version"
)
func TestCheckPre(t *testing.T) {
@@ -34,19 +35,17 @@ func TestCheckPre(t *testing.T) {
t.Fatalf("Error running utils.ExecKubectlCommand: %v", err.Error())
}
var versions map[string]interface{}
var versions map[string]version.Info
if err := json.Unmarshal([]byte(jsonOutput), &versions); err != nil {
t.Fatalf("Error unmarshalling '%s': %v", jsonOutput, err.Error())
t.Fatalf("Error unmarshalling: %v", err.Error())
}
serverGitVersion := strings.TrimPrefix(
versions["serverVersion"].(map[string]interface{})["gitVersion"].(string),
"v")
serverVersion := strings.TrimPrefix(versions["serverVersion"].GitVersion, "v")
cmd := cmdTestCase{
args: "check --pre",
assert: assertGoldenTemplateFile("testdata/check/check_pre.golden", map[string]string{
"serverVersion": serverGitVersion,
"serverVersion": serverVersion,
}),
}
cmd.runTestCmd(t)

View File

@@ -60,7 +60,7 @@ func resourceNamesCompletionFunc(gvk schema.GroupVersionKind) func(cmd *cobra.Co
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
cfg, err := utils.KubeConfig(kubeconfigArgs)
if err != nil {
return completionError(err)
}

View File

@@ -79,12 +79,12 @@ type upsertable interface {
// want to update. The mutate function is nullary -- you mutate a
// value in the closure, e.g., by doing this:
//
// var existing Value
// existing.Name = name
// existing.Namespace = ns
// upsert(ctx, client, valueAdapter{&value}, func() error {
// value.Spec = onePreparedEarlier
// })
// var existing Value
// existing.Name = name
// existing.Namespace = ns
// upsert(ctx, client, valueAdapter{&value}, func() error {
// value.Spec = onePreparedEarlier
// })
func (names apiType) upsert(ctx context.Context, kubeClient client.Client, object upsertable, mutate func() error) (types.NamespacedName, error) {
nsname := types.NamespacedName{
Namespace: object.GetNamespace(),
@@ -117,7 +117,7 @@ func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) e
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) // NB globals
kubeClient, err := utils.KubeClient(kubeconfigArgs) // NB globals
if err != nil {
return err
}

View File

@@ -119,7 +119,7 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -115,7 +115,7 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -21,8 +21,6 @@ import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
@@ -110,26 +108,21 @@ var createHelmReleaseCmd = &cobra.Command{
}
type helmReleaseFlags struct {
name string
source flags.HelmChartSource
dependsOn []string
chart string
chartVersion string
targetNamespace string
createNamespace bool
valuesFiles []string
valuesFrom []string
saName string
crds flags.CRDsPolicy
reconcileStrategy string
chartInterval time.Duration
kubeConfigSecretRef string
name string
source flags.HelmChartSource
dependsOn []string
chart string
chartVersion string
targetNamespace string
createNamespace bool
valuesFiles []string
valuesFrom flags.HelmReleaseValuesFrom
saName string
crds flags.CRDsPolicy
}
var helmReleaseArgs helmReleaseFlags
var supportedHelmReleaseValuesFromKinds = []string{"Secret", "ConfigMap"}
func init() {
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.name, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'")
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.source, "source", helmReleaseArgs.source.Description())
@@ -139,12 +132,9 @@ func init() {
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.targetNamespace, "target-namespace", "", "namespace to install this release, defaults to the HelmRelease namespace")
createHelmReleaseCmd.Flags().BoolVar(&helmReleaseArgs.createNamespace, "create-target-namespace", false, "create the target namespace if it does not exist")
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.saName, "service-account", "", "the name of the service account to impersonate when reconciling this HelmRelease")
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.reconcileStrategy, "reconcile-strategy", "ChartVersion", "the reconcile strategy for helm chart created by the helm release(accepted values: Revision and ChartRevision)")
createHelmReleaseCmd.Flags().DurationVarP(&helmReleaseArgs.chartInterval, "chart-interval", "", 0, "the interval of which to check for new chart versions")
createHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.valuesFiles, "values", nil, "local path to values.yaml files, also accepts comma-separated values")
createHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.valuesFrom, "values-from", nil, "a Kubernetes object reference that contains the values.yaml data key in the format '<kind>/<name>', where kind must be one of: (Secret,ConfigMap)")
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.valuesFrom, "values-from", helmReleaseArgs.valuesFrom.Description())
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.crds, "crds", helmReleaseArgs.crds.Description())
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.kubeConfigSecretRef, "kubeconfig-secret-ref", "", "the name of the Kubernetes Secret that contains a key with the kubeconfig file for connecting to a remote cluster")
createCmd.AddCommand(createHelmReleaseCmd)
}
@@ -164,11 +154,6 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
logger.Generatef("generating HelmRelease")
}
if !validateStrategy(helmReleaseArgs.reconcileStrategy) {
return fmt.Errorf("'%s' is an invalid reconcile strategy(valid: Revision, ChartVersion)",
helmReleaseArgs.reconcileStrategy)
}
helmRelease := helmv2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@@ -192,27 +177,12 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
Name: helmReleaseArgs.source.Name,
Namespace: helmReleaseArgs.source.Namespace,
},
ReconcileStrategy: helmReleaseArgs.reconcileStrategy,
},
},
Suspend: false,
},
}
if helmReleaseArgs.kubeConfigSecretRef != "" {
helmRelease.Spec.KubeConfig = &helmv2.KubeConfig{
SecretRef: meta.SecretKeyReference{
Name: helmReleaseArgs.kubeConfigSecretRef,
},
}
}
if helmReleaseArgs.chartInterval != 0 {
helmRelease.Spec.Chart.Spec.Interval = &metav1.Duration{
Duration: helmReleaseArgs.chartInterval,
}
}
if helmReleaseArgs.createNamespace {
if helmRelease.Spec.Install == nil {
helmRelease.Spec.Install = &helmv2.Install{}
@@ -263,25 +233,11 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
helmRelease.Spec.Values = &apiextensionsv1.JSON{Raw: jsonRaw}
}
if len(helmReleaseArgs.valuesFrom) != 0 {
values := []helmv2.ValuesReference{}
for _, value := range helmReleaseArgs.valuesFrom {
sourceKind, sourceName := utils.ParseObjectKindName(value)
if sourceKind == "" {
return fmt.Errorf("invalid Kubernetes object reference '%s', must be in format <kind>/<name>", value)
}
cleanSourceKind, ok := utils.ContainsEqualFoldItemString(supportedHelmReleaseValuesFromKinds, sourceKind)
if !ok {
return fmt.Errorf("reference kind '%s' is not supported, must be one of: %s",
sourceKind, strings.Join(supportedHelmReleaseValuesFromKinds, ", "))
}
values = append(values, helmv2.ValuesReference{
Name: sourceName,
Kind: cleanSourceKind,
})
}
helmRelease.Spec.ValuesFrom = values
if helmReleaseArgs.valuesFrom.String() != "" {
helmRelease.Spec.ValuesFrom = []helmv2.ValuesReference{{
Kind: helmReleaseArgs.valuesFrom.Kind,
Name: helmReleaseArgs.valuesFrom.Name,
}}
}
if createArgs.export {
@@ -291,7 +247,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}
@@ -360,15 +316,3 @@ func isHelmReleaseReady(ctx context.Context, kubeClient client.Client,
return apimeta.IsStatusConditionTrue(helmRelease.Status.Conditions, meta.ReadyCondition), nil
}
}
func validateStrategy(input string) bool {
allowedStrategy := []string{"Revision", "ChartVersion"}
for _, strategy := range allowedStrategy {
if strategy == input {
return true
}
}
return false
}

View File

@@ -23,7 +23,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var createImageUpdateCmd = &cobra.Command{

View File

@@ -42,21 +42,22 @@ var createKsCmd = &cobra.Command{
Use: "kustomization [name]",
Aliases: []string{"ks"},
Short: "Create or update a Kustomization resource",
Long: "The create command generates a Kustomization resource for a given source.",
Long: "The kustomization source create command generates a Kustomize resource for a given source.",
Example: ` # Create a Kustomization resource from a source at a given path
flux create kustomization kyverno \
--source=GitRepository/kyverno \
--path="./config/release" \
flux create kustomization contour \
--source=GitRepository/contour \
--path="./examples/contour/" \
--prune=true \
--interval=60m \
--wait=true \
--interval=10m \
--health-check="Deployment/contour.projectcontour" \
--health-check="DaemonSet/envoy.projectcontour" \
--health-check-timeout=3m
# Create a Kustomization resource that depends on the previous one
flux create kustomization kyverno-policies \
--depends-on=kyverno \
--source=GitRepository/kyverno-policies \
--path="./policies/flux" \
flux create kustomization webapp \
--depends-on=contour \
--source=GitRepository/webapp \
--path="./deploy/overlays/dev" \
--prune=true \
--interval=5m
@@ -64,14 +65,7 @@ var createKsCmd = &cobra.Command{
flux create kustomization podinfo \
--namespace=default \
--source=GitRepository/podinfo.flux-system \
--path="./kustomize" \
--prune=true \
--interval=5m
# Create a Kustomization resource that references an OCIRepository
flux create kustomization podinfo \
--source=OCIRepository/podinfo \
--target-namespace=default \
--path="./deploy/overlays/dev" \
--prune=true \
--interval=5m
@@ -84,19 +78,18 @@ var createKsCmd = &cobra.Command{
}
type kustomizationFlags struct {
source flags.KustomizationSource
path flags.SafeRelativePath
prune bool
dependsOn []string
validation string
healthCheck []string
healthTimeout time.Duration
saName string
decryptionProvider flags.DecryptionProvider
decryptionSecret string
targetNamespace string
wait bool
kubeConfigSecretRef string
source flags.KustomizationSource
path flags.SafeRelativePath
prune bool
dependsOn []string
validation string
healthCheck []string
healthTimeout time.Duration
saName string
decryptionProvider flags.DecryptionProvider
decryptionSecret string
targetNamespace string
wait bool
}
var kustomizationArgs = NewKustomizationFlags()
@@ -114,7 +107,6 @@ func init() {
createKsCmd.Flags().Var(&kustomizationArgs.decryptionProvider, "decryption-provider", kustomizationArgs.decryptionProvider.Description())
createKsCmd.Flags().StringVar(&kustomizationArgs.decryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption")
createKsCmd.Flags().StringVar(&kustomizationArgs.targetNamespace, "target-namespace", "", "overrides the namespace of all Kustomization objects reconciled by this Kustomization")
createKsCmd.Flags().StringVar(&kustomizationArgs.kubeConfigSecretRef, "kubeconfig-secret-ref", "", "the name of the Kubernetes Secret that contains a key with the kubeconfig file for connecting to a remote cluster")
createKsCmd.Flags().MarkDeprecated("validation", "this arg is no longer used, all resources are validated using server-side apply dry-run")
createCmd.AddCommand(createKsCmd)
@@ -168,14 +160,6 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
},
}
if kustomizationArgs.kubeConfigSecretRef != "" {
kustomization.Spec.KubeConfig = &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
Name: kustomizationArgs.kubeConfigSecretRef,
},
}
}
if len(kustomizationArgs.healthCheck) > 0 && !kustomizationArgs.wait {
healthChecks := make([]meta.NamespacedObjectKindReference, 0)
for _, w := range kustomizationArgs.healthCheck {
@@ -245,7 +229,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -127,7 +127,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -173,7 +173,7 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -96,7 +96,7 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -1,19 +1,3 @@
/*
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 (

View File

@@ -1,121 +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"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/google/go-containerregistry/pkg/name"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
)
var createSecretOCICmd = &cobra.Command{
Use: "oci [name]",
Short: "Create or update a Kubernetes image pull secret",
Long: `The create secret oci command generates a Kubernetes secret that can be used for OCIRepository authentication`,
Example: ` # Create an OCI authentication secret on disk and encrypt it with Mozilla SOPS
flux create secret oci podinfo-auth \
--url=ghcr.io \
--username=username \
--password=password \
--export > repo-auth.yaml
sops --encrypt --encrypted-regex '^(data|stringData)$' \
--in-place repo-auth.yaml
`,
RunE: createSecretOCICmdRun,
}
type secretOCIFlags struct {
url string
password string
username string
}
var secretOCIArgs = secretOCIFlags{}
func init() {
createSecretOCICmd.Flags().StringVar(&secretOCIArgs.url, "url", "", "oci repository address e.g ghcr.io/stefanprodan/charts")
createSecretOCICmd.Flags().StringVarP(&secretOCIArgs.username, "username", "u", "", "basic authentication username")
createSecretOCICmd.Flags().StringVarP(&secretOCIArgs.password, "password", "p", "", "basic authentication password")
createSecretCmd.AddCommand(createSecretOCICmd)
}
func createSecretOCICmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("name is required")
}
secretName := args[0]
if secretOCIArgs.url == "" {
return fmt.Errorf("--url is required")
}
if secretOCIArgs.username == "" {
return fmt.Errorf("--username is required")
}
if secretOCIArgs.password == "" {
return fmt.Errorf("--password is required")
}
if _, err := name.ParseReference(secretOCIArgs.url); err != nil {
return fmt.Errorf("error parsing url: '%s'", err)
}
opts := sourcesecret.Options{
Name: secretName,
Namespace: *kubeconfigArgs.Namespace,
Registry: secretOCIArgs.url,
Password: secretOCIArgs.password,
Username: secretOCIArgs.username,
}
secret, err := sourcesecret.Generate(opts)
if err != nil {
return err
}
if createArgs.export {
rootCmd.Println(secret.Content)
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
var s corev1.Secret
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
return err
}
if err := upsertSecret(ctx, kubeClient, s); err != nil {
return err
}
logger.Actionf("oci secret '%s' created in '%s' namespace", secretName, *kubeconfigArgs.Namespace)
return nil
}

View File

@@ -1,51 +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 (
"testing"
)
func TestCreateSecretOCI(t *testing.T) {
tests := []struct {
name string
args string
assert assertFunc
}{
{
args: "create secret oci",
assert: assertError("name is required"),
},
{
args: "create secret oci ghcr",
assert: assertError("--url is required"),
},
{
args: "create secret oci ghcr --namespace=my-namespace --url ghcr.io --username stefanprodan --password=password --export",
assert: assertGoldenFile("testdata/create_secret/oci/create-secret.yaml"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}

View File

@@ -93,7 +93,7 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -20,7 +20,6 @@ import (
"context"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
@@ -31,9 +30,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
@@ -64,18 +61,17 @@ For Buckets with static authentication, the credentials are stored in a Kubernet
}
type sourceBucketFlags struct {
name string
provider flags.SourceBucketProvider
endpoint string
accessKey string
secretKey string
region string
insecure bool
secretRef string
ignorePaths []string
name string
provider flags.SourceBucketProvider
endpoint string
accessKey string
secretKey string
region string
insecure bool
secretRef string
}
var sourceBucketArgs = newSourceBucketFlags()
var sourceBucketArgs = NewSourceBucketFlags()
func init() {
createSourceBucketCmd.Flags().Var(&sourceBucketArgs.provider, "provider", sourceBucketArgs.provider.Description())
@@ -86,12 +82,11 @@ func init() {
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.region, "region", "", "the bucket region")
createSourceBucketCmd.Flags().BoolVar(&sourceBucketArgs.insecure, "insecure", false, "for when connecting to a non-TLS S3 HTTP endpoint")
createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.secretRef, "secret-ref", "", "the name of an existing secret containing credentials")
createSourceBucketCmd.Flags().StringSliceVar(&sourceBucketArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in bucket resource (can specify multiple paths with commas: path1,path2)")
createSourceCmd.AddCommand(createSourceBucketCmd)
}
func newSourceBucketFlags() sourceBucketFlags {
func NewSourceBucketFlags() sourceBucketFlags {
return sourceBucketFlags{
provider: flags.SourceBucketProvider(sourcev1.GenericBucketProvider),
}
@@ -119,12 +114,6 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
}
defer os.RemoveAll(tmpDir)
var ignorePaths *string
if len(sourceBucketArgs.ignorePaths) > 0 {
ignorePathsStr := strings.Join(sourceBucketArgs.ignorePaths, "\n")
ignorePaths = &ignorePathsStr
}
bucket := &sourcev1.Bucket{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@@ -140,7 +129,6 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
Interval: metav1.Duration{
Duration: createArgs.interval,
},
Ignore: ignorePaths,
},
}
@@ -161,7 +149,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}
@@ -247,30 +235,3 @@ func upsertBucket(ctx context.Context, kubeClient client.Client,
logger.Successf("Bucket source updated")
return namespacedName, nil
}
func isBucketReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, bucket)
if err != nil {
return false, err
}
if c := conditions.Get(bucket, meta.ReadyCondition); c != nil {
// Confirm the Ready condition we are observing is for the
// current generation
if c.ObservedGeneration != bucket.GetGeneration() {
return false, nil
}
// Further check the Status
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -22,23 +22,20 @@ import (
"fmt"
"net/url"
"os"
"strings"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
@@ -60,7 +57,6 @@ type sourceGitFlags struct {
privateKeyFile string
recurseSubmodules bool
silent bool
ignorePaths []string
}
var createSourceGitCmd = &cobra.Command{
@@ -117,7 +113,6 @@ For private Git repositories, the basic authentication credentials are stored in
# Create a source for a Git repository using basic authentication
flux create source git podinfo \
--url=https://github.com/stefanprodan/podinfo \
--branch=master \
--username=username \
--password=password`,
RunE: createSourceGitCmdRun,
@@ -142,7 +137,6 @@ func init() {
createSourceGitCmd.Flags().BoolVar(&sourceGitArgs.recurseSubmodules, "recurse-submodules", false,
"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces")
createSourceGitCmd.Flags().BoolVarP(&sourceGitArgs.silent, "silent", "s", false, "assumes the deploy key is already setup, skips confirmation")
createSourceGitCmd.Flags().StringSliceVar(&sourceGitArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore in git resource (can specify multiple paths with commas: path1,path2)")
createSourceCmd.AddCommand(createSourceGitCmd)
}
@@ -175,7 +169,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
}
if sourceGitArgs.caFile != "" && u.Scheme == "ssh" {
return fmt.Errorf("specifying a CA file is not supported for Git over SSH")
return fmt.Errorf("specifing a CA file is not supported for Git over SSH")
}
if sourceGitArgs.recurseSubmodules && sourceGitArgs.gitImplementation == sourcev1.LibGit2Implementation {
@@ -193,12 +187,6 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return err
}
var ignorePaths *string
if len(sourceGitArgs.ignorePaths) > 0 {
ignorePathsStr := strings.Join(sourceGitArgs.ignorePaths, "\n")
ignorePaths = &ignorePathsStr
}
gitRepository := sourcev1.GitRepository{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@@ -212,7 +200,6 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
},
RecurseSubmodules: sourceGitArgs.recurseSubmodules,
Reference: &sourcev1.GitRepositoryRef{},
Ignore: ignorePaths,
},
}
@@ -245,7 +232,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}
@@ -368,14 +355,7 @@ func isGitRepositoryReady(ctx context.Context, kubeClient client.Client,
return false, err
}
if c := conditions.Get(gitRepository, meta.ReadyCondition); c != nil {
// Confirm the Ready condition we are observing is for the
// current generation
if c.ObservedGeneration != gitRepository.GetGeneration() {
return false, nil
}
// Further check the Status
if c := apimeta.FindStatusCondition(gitRepository.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil

View File

@@ -21,17 +21,15 @@ package main
import (
"context"
"testing"
"time"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
"testing"
"time"
)
var pollInterval = 50 * time.Millisecond
@@ -85,31 +83,6 @@ func (r *reconciler) conditionFunc() (bool, error) {
return true, err
}
func TestCreateSourceGitExport(t *testing.T) {
var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --ignore-paths .cosign,non-existent-dir/ -n default --interval 1m --export --timeout=" + testTimeout.String()
cases := []struct {
name string
args string
assert assertFunc
}{
{
"ExportSucceeded",
command,
assertGoldenFile("testdata/create_source_git/export.golden"),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tc.args,
assert: tc.assert,
}
cmd.runTestCmd(t)
})
}
}
func TestCreateSourceGit(t *testing.T) {
// Default command used for multiple tests
var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --timeout=" + testTimeout.String()
@@ -130,14 +103,7 @@ func TestCreateSourceGit(t *testing.T) {
command,
assertGoldenFile("testdata/create_source_git/success.golden"),
func(repo *sourcev1.GitRepository) {
newCondition := metav1.Condition{
Type: meta.ReadyCondition,
Status: metav1.ConditionTrue,
Reason: sourcev1.GitOperationSucceedReason,
Message: "succeeded message",
ObservedGeneration: repo.GetGeneration(),
}
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
meta.SetResourceCondition(repo, meta.ReadyCondition, metav1.ConditionTrue, sourcev1.GitOperationSucceedReason, "succeeded message")
repo.Status.Artifact = &sourcev1.Artifact{
Path: "some-path",
Revision: "v1",
@@ -148,14 +114,7 @@ func TestCreateSourceGit(t *testing.T) {
command,
assertError("failed message"),
func(repo *sourcev1.GitRepository) {
newCondition := metav1.Condition{
Type: meta.ReadyCondition,
Status: metav1.ConditionFalse,
Reason: sourcev1.URLInvalidReason,
Message: "failed message",
ObservedGeneration: repo.GetGeneration(),
}
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
meta.SetResourceCondition(repo, meta.ReadyCondition, metav1.ConditionFalse, sourcev1.URLInvalidReason, "failed message")
},
}, {
"NoArtifact",
@@ -163,14 +122,7 @@ func TestCreateSourceGit(t *testing.T) {
assertError("GitRepository source reconciliation completed but no artifact was found"),
func(repo *sourcev1.GitRepository) {
// Updated with no artifact
newCondition := metav1.Condition{
Type: meta.ReadyCondition,
Status: metav1.ConditionTrue,
Reason: sourcev1.GitOperationSucceedReason,
Message: "succeeded message",
ObservedGeneration: repo.GetGeneration(),
}
apimeta.SetStatusCondition(&repo.Status.Conditions, newCondition)
meta.SetResourceCondition(repo, meta.ReadyCondition, metav1.ConditionTrue, sourcev1.GitOperationSucceedReason, "succeeded message")
},
},
}

View File

@@ -23,17 +23,17 @@ import (
"os"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
@@ -44,34 +44,23 @@ var createSourceHelmCmd = &cobra.Command{
Short: "Create or update a HelmRepository source",
Long: `The create source helm command generates a HelmRepository resource and waits for it to fetch the index.
For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`,
Example: ` # Create a source for an HTTPS public Helm repository
Example: ` # Create a source for a public Helm repository
flux create source helm podinfo \
--url=https://stefanprodan.github.io/podinfo \
--interval=10m
# Create a source for an HTTPS Helm repository using basic authentication
# Create a source for a Helm repository using basic authentication
flux create source helm podinfo \
--url=https://stefanprodan.github.io/podinfo \
--username=username \
--password=password
# Create a source for an HTTPS Helm repository using TLS authentication
# Create a source for a Helm repository using TLS authentication
flux create source helm podinfo \
--url=https://stefanprodan.github.io/podinfo \
--cert-file=./cert.crt \
--key-file=./key.crt \
--ca-file=./ca.crt
# Create a source for an OCI Helm repository
flux create source helm podinfo \
--url=oci://ghcr.io/stefanprodan/charts/podinfo
--username=username \
--password=password
# Create a source for an OCI Helm repository using an existing secret with basic auth or dockerconfig credentials
flux create source helm podinfo \
--url=oci://ghcr.io/stefanprodan/charts/podinfo
--secret-ref=docker-config`,
--ca-file=./ca.crt`,
RunE: createSourceHelmCmdRun,
}
@@ -95,7 +84,7 @@ func init() {
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.certFile, "cert-file", "", "TLS authentication cert file path")
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.keyFile, "key-file", "", "TLS authentication key file path")
createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.caFile, "ca-file", "", "TLS authentication CA file path")
createSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.secretRef, "secret-ref", "", "", "the name of an existing secret containing TLS, basic auth or docker-config credentials")
createSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.secretRef, "secret-ref", "", "", "the name of an existing secret containing TLS or basic auth credentials")
createSourceHelmCmd.Flags().BoolVarP(&sourceHelmArgs.passCredentials, "pass-credentials", "", false, "pass credentials to all domains")
createSourceCmd.AddCommand(createSourceHelmCmd)
@@ -137,14 +126,6 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
},
}
url, err := url.Parse(sourceHelmArgs.url)
if err != nil {
return fmt.Errorf("failed to parse URL: %w", err)
}
if url.Scheme == sourcev1.HelmRepositoryTypeOCI {
helmRepository.Spec.Type = sourcev1.HelmRepositoryTypeOCI
}
if createSourceArgs.fetchTimeout > 0 {
helmRepository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}
}
@@ -163,7 +144,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}
@@ -215,11 +196,6 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
}
logger.Successf("HelmRepository source reconciliation completed")
if helmRepository.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
// OCI repos don't expose any artifact so we just return early here
return nil
}
if helmRepository.Status.Artifact == nil {
return fmt.Errorf("HelmRepository source reconciliation completed but no artifact was found")
}
@@ -266,14 +242,12 @@ func isHelmRepositoryReady(ctx context.Context, kubeClient client.Client,
return false, err
}
if c := conditions.Get(helmRepository, meta.ReadyCondition); c != nil {
// Confirm the Ready condition we are observing is for the
// current generation
if c.ObservedGeneration != helmRepository.GetGeneration() {
return false, nil
}
// Confirm the state we are observing is for the current generation
if helmRepository.Generation != helmRepository.Status.ObservedGeneration {
return false, nil
}
// Further check the Status
if c := apimeta.FindStatusCondition(helmRepository.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil

View File

@@ -1,81 +0,0 @@
//go:build unit
// +build unit
/*
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 (
"testing"
)
func TestCreateSourceHelm(t *testing.T) {
tests := []struct {
name string
args string
resultFile string
assertFunc string
}{
{
name: "no args",
args: "create source helm",
resultFile: "name is required",
assertFunc: "assertError",
},
{
name: "OCI repo",
args: "create source helm podinfo --url=oci://ghcr.io/stefanprodan/charts/podinfo --interval 5m --export",
resultFile: "./testdata/create_source_helm/oci.golden",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "OCI repo with Secret ref",
args: "create source helm podinfo --url=oci://ghcr.io/stefanprodan/charts/podinfo --interval 5m --secret-ref=creds --export",
resultFile: "./testdata/create_source_helm/oci-with-secret.golden",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "HTTPS repo",
args: "create source helm podinfo --url=https://stefanprodan.github.io/charts/podinfo --interval 5m --export",
resultFile: "./testdata/create_source_helm/https.golden",
assertFunc: "assertGoldenTemplateFile",
},
}
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
setup(t, tmpl)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var assert assertFunc
switch tt.assertFunc {
case "assertGoldenTemplateFile":
assert = assertGoldenTemplateFile(tt.resultFile, tmpl)
case "assertError":
assert = assertError(tt.resultFile)
}
cmd := cmdTestCase{
args: tt.args + " -n " + tmpl["fluxns"],
assert: assert,
}
cmd.runTestCmd(t)
})
}
}

View File

@@ -1,247 +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"
"strings"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
)
var createSourceOCIRepositoryCmd = &cobra.Command{
Use: "oci [name]",
Short: "Create or update an OCIRepository",
Long: `The create source oci command generates an OCIRepository resource and waits for it to be ready.`,
Example: ` # Create an OCIRepository for a public container image
flux create source oci podinfo \
--url=oci://ghcr.io/stefanprodan/manifests/podinfo \
--tag=6.1.6 \
--interval=10m
`,
RunE: createSourceOCIRepositoryCmdRun,
}
type sourceOCIRepositoryFlags struct {
url string
tag string
semver string
digest string
secretRef string
serviceAccount string
certSecretRef string
ignorePaths []string
provider flags.SourceOCIProvider
insecure bool
}
var sourceOCIRepositoryArgs = newSourceOCIFlags()
func newSourceOCIFlags() sourceOCIRepositoryFlags {
return sourceOCIRepositoryFlags{
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
}
}
func init() {
createSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.provider, "provider", sourceOCIRepositoryArgs.provider.Description())
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.url, "url", "", "the OCI repository URL")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.tag, "tag", "", "the OCI artifact tag")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.semver, "tag-semver", "", "the OCI artifact tag semver range")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.digest, "digest", "", "the OCI artifact digest")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.serviceAccount, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates")
createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)")
createSourceOCIRepositoryCmd.Flags().BoolVar(&sourceOCIRepositoryArgs.insecure, "insecure", false, "for when connecting to a non-TLS registries over plain HTTP")
createSourceCmd.AddCommand(createSourceOCIRepositoryCmd)
}
func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
name := args[0]
if sourceOCIRepositoryArgs.url == "" {
return fmt.Errorf("url is required")
}
if sourceOCIRepositoryArgs.semver == "" && sourceOCIRepositoryArgs.tag == "" && sourceOCIRepositoryArgs.digest == "" {
return fmt.Errorf("--tag, --tag-semver or --digest is required")
}
sourceLabels, err := parseLabels()
if err != nil {
return err
}
var ignorePaths *string
if len(sourceOCIRepositoryArgs.ignorePaths) > 0 {
ignorePathsStr := strings.Join(sourceOCIRepositoryArgs.ignorePaths, "\n")
ignorePaths = &ignorePathsStr
}
repository := &sourcev1.OCIRepository{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Labels: sourceLabels,
},
Spec: sourcev1.OCIRepositorySpec{
Provider: sourceOCIRepositoryArgs.provider.String(),
URL: sourceOCIRepositoryArgs.url,
Insecure: sourceOCIRepositoryArgs.insecure,
Interval: metav1.Duration{
Duration: createArgs.interval,
},
Reference: &sourcev1.OCIRepositoryRef{},
Ignore: ignorePaths,
},
}
if digest := sourceOCIRepositoryArgs.digest; digest != "" {
repository.Spec.Reference.Digest = digest
}
if semver := sourceOCIRepositoryArgs.semver; semver != "" {
repository.Spec.Reference.SemVer = semver
}
if tag := sourceOCIRepositoryArgs.tag; tag != "" {
repository.Spec.Reference.Tag = tag
}
if createSourceArgs.fetchTimeout > 0 {
repository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}
}
if saName := sourceOCIRepositoryArgs.serviceAccount; saName != "" {
repository.Spec.ServiceAccountName = saName
}
if secretName := sourceOCIRepositoryArgs.secretRef; secretName != "" {
repository.Spec.SecretRef = &meta.LocalObjectReference{
Name: secretName,
}
}
if secretName := sourceOCIRepositoryArgs.certSecretRef; secretName != "" {
repository.Spec.CertSecretRef = &meta.LocalObjectReference{
Name: secretName,
}
}
if createArgs.export {
return printExport(exportOCIRepository(repository))
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
logger.Actionf("applying OCIRepository")
namespacedName, err := upsertOCIRepository(ctx, kubeClient, repository)
if err != nil {
return err
}
logger.Waitingf("waiting for OCIRepository reconciliation")
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isOCIRepositoryReady(ctx, kubeClient, namespacedName, repository)); err != nil {
return err
}
logger.Successf("OCIRepository reconciliation completed")
if repository.Status.Artifact == nil {
return fmt.Errorf("no artifact was found")
}
logger.Successf("fetched revision: %s", repository.Status.Artifact.Revision)
return nil
}
func upsertOCIRepository(ctx context.Context, kubeClient client.Client,
ociRepository *sourcev1.OCIRepository) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: ociRepository.GetNamespace(),
Name: ociRepository.GetName(),
}
var existing sourcev1.OCIRepository
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, ociRepository); err != nil {
return namespacedName, err
} else {
logger.Successf("OCIRepository created")
return namespacedName, nil
}
}
return namespacedName, err
}
existing.Labels = ociRepository.Labels
existing.Spec = ociRepository.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return namespacedName, err
}
ociRepository = &existing
logger.Successf("OCIRepository updated")
return namespacedName, nil
}
func isOCIRepositoryReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, ociRepository *sourcev1.OCIRepository) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, ociRepository)
if err != nil {
return false, err
}
if c := conditions.Get(ociRepository, meta.ReadyCondition); c != nil {
// Confirm the Ready condition we are observing is for the
// current generation
if c.ObservedGeneration != ociRepository.GetGeneration() {
return false, nil
}
// Further check the Status
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -1,61 +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 (
"testing"
)
func TestCreateSourceOCI(t *testing.T) {
tests := []struct {
name string
args string
assertFunc assertFunc
}{
{
name: "NoArgs",
args: "create source oci",
assertFunc: assertError("name is required"),
},
{
name: "NoURL",
args: "create source oci podinfo",
assertFunc: assertError("url is required"),
},
{
name: "export manifest",
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --export",
assertFunc: assertGoldenFile("./testdata/oci/export.golden"),
},
{
name: "export manifest with secret",
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --secret-ref=creds --export",
assertFunc: assertGoldenFile("./testdata/oci/export_with_secret.golden"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assertFunc,
}
cmd.runTestCmd(t)
})
}
}

View File

@@ -156,7 +156,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -60,7 +60,7 @@ func (del deleteCommand) run(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -27,7 +27,7 @@ var deleteKsCmd = &cobra.Command{
Aliases: []string{"ks"},
Short: "Delete a Kustomization resource",
Long: "The delete kustomization command deletes the given Kustomization from the cluster.",
Example: ` # Delete a kustomization and the Kubernetes resources created by it when prune is enabled
Example: ` # Delete a kustomization and the Kubernetes resources created by it
flux delete kustomization podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: deleteCommand{

View File

@@ -19,7 +19,7 @@ package main
import (
"github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var deleteSourceBucketCmd = &cobra.Command{

View File

@@ -19,7 +19,7 @@ package main
import (
"github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var deleteSourceGitCmd = &cobra.Command{

View File

@@ -19,7 +19,7 @@ package main
import (
"github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var deleteSourceHelmCmd = &cobra.Command{

View File

@@ -1,40 +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 (
"github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var deleteSourceOCIRepositoryCmd = &cobra.Command{
Use: "oci [name]",
Short: "Delete an OCIRepository source",
Long: "The delete source oci command deletes the given OCIRepository from the cluster.",
Example: ` # Delete an OCIRepository
flux delete source oci podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
RunE: deleteCommand{
apiType: ociRepositoryType,
object: universalAdapter{&sourcev1.OCIRepository{}},
}.run,
}
func init() {
deleteSourceCmd.AddCommand(deleteSourceOCIRepositoryCmd)
}

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

@@ -34,26 +34,21 @@ var diffKsCmd = &cobra.Command{
Long: `The diff command does a build, then it performs a server-side dry-run and prints the diff.
Exit status: 0 No differences were found. 1 Differences were found. >1 diff failed with an error.`,
Example: `# Preview local changes as they were applied on the cluster
flux diff kustomization my-app --path ./path/to/local/manifests
# Preview using a local flux kustomization file
flux diff kustomization my-app --path ./path/to/local/manifests --kustomization-file ./path/to/local/my-app.yaml`,
flux diff kustomization my-app --path ./path/to/local/manifests`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: diffKsCmdRun,
}
type diffKsFlags struct {
kustomizationFile string
path string
progressBar bool
path string
progressBar bool
}
var diffKsArgs diffKsFlags
func init() {
diffKsCmd.Flags().StringVar(&diffKsArgs.path, "path", "", "Path to a local directory that matches the specified Kustomization.spec.path.")
diffKsCmd.Flags().StringVar(&diffKsArgs.path, "path", "", "Path to a local directory that matches the specified Kustomization.spec.path.)")
diffKsCmd.Flags().BoolVar(&diffKsArgs.progressBar, "progress-bar", true, "Boolean to set the progress bar. The default value is true.")
diffKsCmd.Flags().StringVar(&diffKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.")
diffCmd.AddCommand(diffKsCmd)
}
@@ -71,18 +66,12 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
return &RequestError{StatusCode: 2, Err: fmt.Errorf("invalid resource path %q", diffKsArgs.path)}
}
if diffKsArgs.kustomizationFile != "" {
if fs, err := os.Stat(diffKsArgs.kustomizationFile); os.IsNotExist(err) || fs.IsDir() {
return fmt.Errorf("invalid kustomization file %q", diffKsArgs.kustomizationFile)
}
}
var builder *build.Builder
var err error
if diffKsArgs.progressBar {
builder, err = build.NewBuilder(kubeconfigArgs, kubeclientOptions, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout), build.WithKustomizationFile(diffKsArgs.kustomizationFile), build.WithProgressBar())
builder, err = build.NewBuilder(kubeconfigArgs, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout), build.WithProgressBar())
} else {
builder, err = build.NewBuilder(kubeconfigArgs, kubeclientOptions, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout), build.WithKustomizationFile(diffKsArgs.kustomizationFile))
builder, err = build.NewBuilder(kubeconfigArgs, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout))
}
if err != nil {

View File

@@ -97,7 +97,7 @@ func TestDiffKustomization(t *testing.T) {
"fluxns": allocateNamespace("flux-system"),
}
b, _ := build.NewBuilder(kubeconfigArgs, kubeclientOptions, "podinfo", "")
b, _ := build.NewBuilder(kubeconfigArgs, "podinfo", "")
resourceManager, err := b.Manager()
if err != nil {

View File

@@ -74,7 +74,7 @@ func (export exportCommand) run(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -59,7 +59,7 @@ func (export exportWithSecretCommand) run(cmd *cobra.Command, args []string) err
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -21,7 +21,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var exportSourceBucketCmd = &cobra.Command{

View File

@@ -21,7 +21,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var exportSourceGitCmd = &cobra.Command{

View File

@@ -21,7 +21,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var exportSourceHelmCmd = &cobra.Command{

View File

@@ -1,92 +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 (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var exportSourceOCIRepositoryCmd = &cobra.Command{
Use: "oci [name]",
Short: "Export OCIRepository sources in YAML format",
Long: "The export source oci command exports one or all OCIRepository sources in YAML format.",
Example: ` # Export all OCIRepository sources
flux export source oci --all > sources.yaml
# Export a OCIRepository including the static credentials
flux export source oci my-app --with-credentials > source.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
RunE: exportWithSecretCommand{
list: ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
object: ociRepositoryAdapter{&sourcev1.OCIRepository{}},
}.run,
}
func init() {
exportSourceCmd.AddCommand(exportSourceOCIRepositoryCmd)
}
func exportOCIRepository(source *sourcev1.OCIRepository) interface{} {
gvk := sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)
export := sourcev1.OCIRepository{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: source.Name,
Namespace: source.Namespace,
Labels: source.Labels,
Annotations: source.Annotations,
},
Spec: source.Spec,
}
return export
}
func getOCIRepositorySecret(source *sourcev1.OCIRepository) *types.NamespacedName {
if source.Spec.SecretRef != nil {
namespacedName := types.NamespacedName{
Namespace: source.Namespace,
Name: source.Spec.SecretRef.Name,
}
return &namespacedName
}
return nil
}
func (ex ociRepositoryAdapter) secret() *types.NamespacedName {
return getOCIRepositorySecret(ex.OCIRepository)
}
func (ex ociRepositoryListAdapter) secretItem(i int) *types.NamespacedName {
return getOCIRepositorySecret(&ex.OCIRepositoryList.Items[i])
}
func (ex ociRepositoryAdapter) export() interface{} {
return exportOCIRepository(ex.OCIRepository)
}
func (ex ociRepositoryListAdapter) exportItem(i int) interface{} {
return exportOCIRepository(&ex.OCIRepositoryList.Items[i])
}

View File

@@ -33,7 +33,6 @@ import (
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/printers"
)
type deriveType func(runtime.Object) (summarisable, error)
@@ -136,7 +135,7 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}
@@ -162,9 +161,7 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
}
if get.list.len() == 0 {
if len(args) > 0 {
logger.Failuref("%s object '%s' not found in '%s' namespace", get.kind, args[0], *kubeconfigArgs.Namespace)
} else if !getAll {
if !getAll {
logger.Failuref("no %s objects found in %s namespace", get.kind, *kubeconfigArgs.Namespace)
}
return nil
@@ -180,10 +177,7 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
return err
}
err = printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows)
if err != nil {
return err
}
utils.PrintTable(cmd.OutOrStdout(), header, rows)
if getAll {
fmt.Println()
@@ -214,6 +208,7 @@ func getRowsToPrint(getAll bool, list summarisable) ([][]string, error) {
return rows, nil
}
//
// watch starts a client-side watch of one or more resources.
func (get *getCommand) watch(ctx context.Context, kubeClient client.WithWatch, cmd *cobra.Command, args []string, listOpts []client.ListOption) error {
w, err := kubeClient.Watch(ctx, get.list.asClientList(), listOpts...)
@@ -247,16 +242,10 @@ func watchUntil(ctx context.Context, w watch.Interface, get *getCommand) (bool,
return false, err
}
if firstIteration {
err = printers.TablePrinter(header).Print(os.Stdout, rows)
if err != nil {
return false, err
}
utils.PrintTable(os.Stdout, header, rows)
firstIteration = false
} else {
err = printers.TablePrinter([]string{}).Print(os.Stdout, rows)
if err != nil {
return false, err
}
utils.PrintTable(os.Stdout, []string{}, rows)
}
return false, nil

View File

@@ -77,11 +77,11 @@ func init() {
func (s alertListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := s.Items[i]
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind), strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
return append(nameColumns(&item, includeNamespace, includeKind), status, msg, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (s alertListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Suspended", "Ready", "Message"}
headers := []string{"Name", "Ready", "Message", "Suspended"}
if includeNamespace {
return append(namespaceHeader, headers...)
}

View File

@@ -75,11 +75,11 @@ func (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool, incl
revision := item.Status.LastAppliedRevision
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (a helmReleaseListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}

View File

@@ -74,11 +74,11 @@ func init() {
func (s imagePolicyListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := s.Items[i]
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind), item.Status.LatestImage, status, msg)
return append(nameColumns(&item, includeNamespace, includeKind), status, msg, item.Status.LatestImage)
}
func (s imagePolicyListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Latest image", "Ready", "Message"}
headers := []string{"Name", "Ready", "Message", "Latest image"}
if includeNamespace {
return append(namespaceHeader, headers...)
}

View File

@@ -82,11 +82,11 @@ func (s imageRepositoryListAdapter) summariseItem(i int, includeNamespace bool,
lastScan = item.Status.LastScanResult.ScanTime.Time.Format(time.RFC3339)
}
return append(nameColumns(&item, includeNamespace, includeKind),
lastScan, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
status, msg, lastScan, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (s imageRepositoryListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Last scan", "Suspended", "Ready", "Message"}
headers := []string{"Name", "Ready", "Message", "Last scan", "Suspended"}
if includeNamespace {
return append(namespaceHeader, headers...)
}

View File

@@ -81,11 +81,11 @@ func (s imageUpdateAutomationListAdapter) summariseItem(i int, includeNamespace
if item.Status.LastAutomationRunTime != nil {
lastRun = item.Status.LastAutomationRunTime.Time.Format(time.RFC3339)
}
return append(nameColumns(&item, includeNamespace, includeKind), lastRun, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
return append(nameColumns(&item, includeNamespace, includeKind), status, msg, lastRun, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (s imageUpdateAutomationListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Last run", "Suspended", "Ready", "Message"}
headers := []string{"Name", "Ready", "Message", "Last run", "Suspended"}
if includeNamespace {
return append(namespaceHeader, headers...)
}

View File

@@ -85,11 +85,11 @@ func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool, in
msg = shortenCommitSha(msg)
}
return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (a kustomizationListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}

View File

@@ -74,11 +74,11 @@ func init() {
func (s receiverListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := s.Items[i]
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind), strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
return append(nameColumns(&item, includeNamespace, includeKind), status, msg, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (s receiverListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Suspended", "Ready", "Message"}
headers := []string{"Name", "Ready", "Message", "Suspended"}
if includeNamespace {
return append(namespaceHeader, headers...)
}

View File

@@ -21,7 +21,7 @@ import (
"github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var getSourceAllCmd = &cobra.Command{
@@ -40,10 +40,6 @@ var getSourceAllCmd = &cobra.Command{
}
var allSourceCmd = []getCommand{
{
apiType: ociRepositoryType,
list: &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
},
{
apiType: bucketType,
list: &bucketListAdapter{&sourcev1.BucketList{}},

View File

@@ -24,7 +24,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var getSourceBucketCmd = &cobra.Command{
@@ -81,11 +81,11 @@ func (a *bucketListAdapter) summariseItem(i int, includeNamespace bool, includeK
}
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (a bucketListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}

View File

@@ -24,7 +24,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var getSourceHelmChartCmd = &cobra.Command{
@@ -81,11 +81,11 @@ func (a *helmChartListAdapter) summariseItem(i int, includeNamespace bool, inclu
}
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (a helmChartListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}

View File

@@ -25,7 +25,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var getSourceGitCmd = &cobra.Command{
@@ -86,11 +86,11 @@ func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i
msg = shortenCommitSha(msg)
}
return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (a gitRepositoryListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}

View File

@@ -24,7 +24,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var getSourceHelmCmd = &cobra.Command{
@@ -81,11 +81,11 @@ func (a *helmRepositoryListAdapter) summariseItem(i int, includeNamespace bool,
}
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (a helmRepositoryListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}

View File

@@ -1,98 +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 (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var getSourceOCIRepositoryCmd = &cobra.Command{
Use: "oci",
Short: "Get OCIRepository status",
Long: "The get sources oci command prints the status of the OCIRepository sources.",
Example: ` # List all OCIRepositories and their status
flux get sources oci
# List OCIRepositories from all namespaces
flux get sources oci --all-namespaces`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
RunE: func(cmd *cobra.Command, args []string) error {
get := getCommand{
apiType: ociRepositoryType,
list: &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*sourcev1.OCIRepository)
if !ok {
return nil, fmt.Errorf("impossible to cast type %#v to OCIRepository", obj)
}
sink := &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{
Items: []sourcev1.OCIRepository{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
}
func init() {
getSourceCmd.AddCommand(getSourceOCIRepositoryCmd)
}
func (a *ociRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := a.Items[i]
var revision string
if item.GetArtifact() != nil {
revision = item.GetArtifact().Revision
}
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (a ociRepositoryListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}
return headers
}
func (a ociRepositoryListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := a.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}

View File

@@ -1,22 +1,6 @@
//go:build e2e
// +build e2e
/*
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 "testing"

View File

@@ -27,7 +27,6 @@ import (
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/status"
)
@@ -38,13 +37,10 @@ var installCmd = &cobra.Command{
Long: `The install command deploys Flux in the specified namespace.
If a previous version is installed, then an in-place upgrade will be performed.`,
Example: ` # Install the latest version in the flux-system namespace
flux install --namespace=flux-system
flux install --version=latest --namespace=flux-system
# Install a specific series of components
flux install --components="source-controller,kustomize-controller"
# Install all components including the image automation ones
flux install --components-extra="image-reflector-controller,image-automation-controller"
# Install a specific version and a series of components
flux install --version=v0.0.7 --components="source-controller,kustomize-controller"
# Install Flux onto tainted Kubernetes nodes
flux install --toleration-keys=node.kubernetes.io/dedicated-to-flux
@@ -88,7 +84,7 @@ func init() {
installCmd.Flags().StringSliceVar(&installArgs.defaultComponents, "components", rootArgs.defaults.Components,
"list of components, accepts comma-separated values")
installCmd.Flags().StringSliceVar(&installArgs.extraComponents, "components-extra", nil,
"list of components in addition to those supplied or defaulted, accepts values such as 'image-reflector-controller,image-automation-controller'")
"list of components in addition to those supplied or defaulted, accepts comma-separated values")
installCmd.Flags().StringVar(&installArgs.manifestsPath, "manifests", "", "path to the manifest directory")
installCmd.Flags().StringVar(&installArgs.registry, "registry", rootArgs.defaults.Registry,
"container registry where the toolkit images are published")
@@ -135,7 +131,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Generatef("generating manifests")
}
tmpDir, err := manifestgen.MkdirTempAbs("", *kubeconfigArgs.Namespace)
tmpDir, err := os.MkdirTemp("", *kubeconfigArgs.Namespace)
if err != nil {
return err
}
@@ -194,14 +190,14 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
return nil
}
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, filepath.Join(tmpDir, manifest.Path))
if err != nil {
return fmt.Errorf("install failed: %w", err)
}
fmt.Fprintln(os.Stderr, applyOutput)
kubeConfig, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
kubeConfig, err := utils.KubeConfig(kubeconfigArgs)
if err != nil {
return fmt.Errorf("install failed: %w", err)
}

View File

@@ -1,53 +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 "testing"
func TestInstall(t *testing.T) {
// The pointer to kubeconfigArgs.Namespace is shared across
// the tests. When a new value is set, it will linger and
// impact subsequent tests.
// Given that this test uses an invalid namespace, it ensures
// to restore whatever value it had previously.
currentNamespace := *kubeconfigArgs.Namespace
defer func() {
*kubeconfigArgs.Namespace = currentNamespace
}()
tests := []struct {
name string
args string
assert assertFunc
}{
{
name: "invalid namespace",
args: "install --namespace='@#[]'",
assert: assertError("namespace must be a valid DNS label: \"@#[]\""),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}

View File

@@ -1,31 +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 (
"github.com/spf13/cobra"
)
var listCmd = &cobra.Command{
Use: "list",
Short: "List artifacts",
Long: "The list command is used for printing the OCI artifacts metadata.",
}
func init() {
rootCmd.AddCommand(listCmd)
}

View File

@@ -1,123 +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"
"github.com/fluxcd/flux2/internal/flags"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/spf13/cobra"
oci "github.com/fluxcd/pkg/oci/client"
"github.com/fluxcd/flux2/pkg/printers"
)
type listArtifactFlags struct {
semverFilter string
regexFilter string
creds string
provider flags.SourceOCIProvider
}
var listArtifactArgs = newListArtifactFlags()
func newListArtifactFlags() listArtifactFlags {
return listArtifactFlags{
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
}
}
var listArtifactsCmd = &cobra.Command{
Use: "artifacts",
Short: "list artifacts",
Long: `The list command fetches the tags and their metadata from a remote 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.`,
Example: ` # List the artifacts stored in an OCI repository
flux list artifact oci://ghcr.io/org/config/app
`,
RunE: listArtifactsCmdRun,
}
func init() {
listArtifactsCmd.Flags().StringVar(&listArtifactArgs.semverFilter, "filter-semver", "", "filter tags returned from the oci repository using semver")
listArtifactsCmd.Flags().StringVar(&listArtifactArgs.regexFilter, "filter-regex", "", "filter tags returned from the oci repository using regex")
listArtifactsCmd.Flags().StringVar(&listArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
listArtifactsCmd.Flags().Var(&listArtifactArgs.provider, "provider", listArtifactArgs.provider.Description())
listCmd.AddCommand(listArtifactsCmd)
}
func listArtifactsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("artifact repository URL is required")
}
ociURL := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
url, err := oci.ParseArtifactURL(ociURL)
if err != nil {
return err
}
ociClient := oci.NewLocalClient()
if listArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && listArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials")
if err := ociClient.LoginWithCredentials(listArtifactArgs.creds); err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
}
}
if listArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials")
ociProvider, err := listArtifactArgs.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)
}
}
opts := oci.ListOptions{
RegexFilter: listArtifactArgs.regexFilter,
SemverFilter: listArtifactArgs.semverFilter,
}
metas, err := ociClient.List(ctx, url, opts)
if err != nil {
return err
}
var rows [][]string
for _, meta := range metas {
rows = append(rows, []string{meta.URL, meta.Digest, meta.Source, meta.Revision})
}
err = printers.TablePrinter([]string{"artifact", "digest", "source", "revision"}).Print(cmd.OutOrStdout(), rows)
if err != nil {
return err
}
return nil
}

View File

@@ -80,8 +80,6 @@ var logsArgs = &logsFlags{
tail: -1,
}
const controllerContainer = "manager"
func init() {
logsCmd.Flags().Var(&logsArgs.logLevel, "level", logsArgs.logLevel.Description())
logsCmd.Flags().StringVarP(&logsArgs.kind, "kind", "", logsArgs.kind, "displays errors of a particular toolkit kind e.g GitRepository")
@@ -101,7 +99,7 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
cfg, err := utils.KubeConfig(kubeconfigArgs)
if err != nil {
return err
}
@@ -148,10 +146,6 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
var requests []rest.ResponseWrapper
for _, pod := range pods {
logOpts := logOpts.DeepCopy()
if len(pod.Spec.Containers) > 1 {
logOpts.Container = controllerContainer
}
req := clientset.CoreV1().Pods(logsArgs.fluxNamespace).GetLogs(pod.Name, logOpts)
requests = append(requests, req)
}
@@ -204,10 +198,12 @@ func parallelPodLogs(ctx context.Context, requests []rest.ResponseWrapper) error
wg := &sync.WaitGroup{}
wg.Add(len(requests))
var mutex = &sync.Mutex{}
for _, request := range requests {
go func(req rest.ResponseWrapper) {
defer wg.Done()
if err := logRequest(ctx, req, writer); err != nil {
if err := logRequest(mutex, ctx, req, os.Stdout); err != nil {
writer.CloseWithError(err)
return
}
@@ -224,8 +220,9 @@ func parallelPodLogs(ctx context.Context, requests []rest.ResponseWrapper) error
}
func podLogs(ctx context.Context, requests []rest.ResponseWrapper) error {
mutex := &sync.Mutex{}
for _, req := range requests {
if err := logRequest(ctx, req, os.Stdout); err != nil {
if err := logRequest(mutex, ctx, req, os.Stdout); err != nil {
return err
}
}
@@ -243,7 +240,7 @@ func createLabelStringFromMap(m map[string]string) string {
return strings.Join(strArr, ",")
}
func logRequest(ctx context.Context, request rest.ResponseWrapper, w io.Writer) error {
func logRequest(mu *sync.Mutex, ctx context.Context, request rest.ResponseWrapper, w io.Writer) error {
stream, err := request.Stream(ctx)
if err != nil {
return err
@@ -252,13 +249,12 @@ func logRequest(ctx context.Context, request rest.ResponseWrapper, w io.Writer)
scanner := bufio.NewScanner(stream)
const logTmpl = "{{.Timestamp}} {{.Level}} {{or .Kind .ControllerKind}}{{if .Name}}/{{.Name}}.{{.Namespace}}{{end}} - {{.Message}} {{.Error}}\n"
const logTmpl = "{{.Timestamp}} {{.Level}} {{.Kind}}{{if .Name}}/{{.Name}}.{{.Namespace}}{{end}} - {{.Message}} {{.Error}}\n"
t, err := template.New("log").Parse(logTmpl)
if err != nil {
return fmt.Errorf("unable to create template, err: %s", err)
}
bw := bufio.NewWriter(w)
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "{") {
@@ -269,33 +265,36 @@ func logRequest(ctx context.Context, request rest.ResponseWrapper, w io.Writer)
logger.Failuref("parse error: %s", err)
break
}
filterPrintLog(t, &l, bw)
bw.Flush()
mu.Lock()
filterPrintLog(t, &l)
mu.Unlock()
}
return nil
}
func filterPrintLog(t *template.Template, l *ControllerLogEntry, w io.Writer) {
if (logsArgs.logLevel == "" || logsArgs.logLevel == l.Level) &&
(logsArgs.kind == "" || strings.EqualFold(logsArgs.kind, l.Kind) || strings.EqualFold(logsArgs.kind, l.ControllerKind)) &&
(logsArgs.name == "" || strings.EqualFold(logsArgs.name, l.Name)) &&
(logsArgs.allNamespaces || strings.EqualFold(*kubeconfigArgs.Namespace, l.Namespace)) {
err := t.Execute(w, l)
if err != nil {
logger.Failuref("log template error: %s", err)
}
func filterPrintLog(t *template.Template, l *ControllerLogEntry) {
if logsArgs.logLevel != "" && logsArgs.logLevel != l.Level ||
logsArgs.kind != "" && strings.ToLower(logsArgs.kind) != strings.ToLower(l.Kind) ||
logsArgs.name != "" && strings.ToLower(logsArgs.name) != strings.ToLower(l.Name) ||
!logsArgs.allNamespaces && strings.ToLower(*kubeconfigArgs.Namespace) != strings.ToLower(l.Namespace) {
return
}
err := t.Execute(os.Stdout, l)
if err != nil {
logger.Failuref("log template error: %s", err)
}
}
type ControllerLogEntry struct {
Timestamp string `json:"ts"`
Level flags.LogLevel `json:"level"`
Message string `json:"msg"`
Error string `json:"error,omitempty"`
Logger string `json:"logger"`
Kind string `json:"reconciler kind,omitempty"`
ControllerKind string `json:"controllerKind,omitempty"`
Name string `json:"name,omitempty"`
Namespace string `json:"namespace,omitempty"`
Timestamp string `json:"ts"`
Level flags.LogLevel `json:"level"`
Message string `json:"msg"`
Error string `json:"error,omitempty"`
Logger string `json:"logger"`
Kind string `json:"reconciler kind,omitempty"`
Name string `json:"name,omitempty"`
Namespace string `json:"namespace,omitempty"`
}

View File

@@ -20,14 +20,7 @@ limitations under the License.
package main
import (
"bytes"
"context"
"io"
"os"
"strings"
"testing"
. "github.com/onsi/gomega"
)
func TestLogsNoArgs(t *testing.T) {
@@ -85,106 +78,3 @@ func TestLogsSinceOnlyOneAllowed(t *testing.T) {
}
cmd.runTestCmd(t)
}
func TestLogRequest(t *testing.T) {
mapper := &testResponseMapper{}
tests := []struct {
name string
namespace string
flags *logsFlags
assertFile string
}{
{
name: "all logs",
flags: &logsFlags{
tail: -1,
allNamespaces: true,
},
assertFile: "testdata/logs/all-logs.txt",
},
{
name: "filter by namespace",
namespace: "default",
flags: &logsFlags{
tail: -1,
},
assertFile: "testdata/logs/namespace.txt",
},
{
name: "filter by kind and namespace",
flags: &logsFlags{
tail: -1,
kind: "Kustomization",
},
assertFile: "testdata/logs/kind.txt",
},
{
name: "filter by loglevel",
flags: &logsFlags{
tail: -1,
logLevel: "error",
allNamespaces: true,
},
assertFile: "testdata/logs/log-level.txt",
},
{
name: "filter by namespace, name, loglevel and kind",
namespace: "flux-system",
flags: &logsFlags{
tail: -1,
logLevel: "error",
kind: "Kustomization",
name: "podinfo",
},
assertFile: "testdata/logs/multiple-filters.txt",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
logsArgs = tt.flags
if tt.namespace != "" {
*kubeconfigArgs.Namespace = tt.namespace
}
w := bytes.NewBuffer([]byte{})
err := logRequest(context.Background(), mapper, w)
g.Expect(err).To(BeNil())
got := make([]byte, w.Len())
_, err = w.Read(got)
g.Expect(err).To(BeNil())
expected, err := os.ReadFile(tt.assertFile)
g.Expect(err).To(BeNil())
g.Expect(string(got)).To(Equal(string(expected)))
// reset flags to default
*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace
logsArgs = &logsFlags{
tail: -1,
}
})
}
}
var testPodLogs = `{"level":"info","ts":"2022-08-02T12:55:34.419Z","msg":"no changes since last reconcilation: observed revision","controller":"gitrepository","controllerGroup":"source.toolkit.fluxcd.io","controllerKind":"GitRepository","gitRepository":{"name":"podinfo","namespace":"default"},"namespace":"default","name":"podinfo","reconcileID":"5ef9b2ef-4ea5-47b7-b887-a247cafc1bce"}
{"level":"error","ts":"2022-08-02T12:56:04.679Z","logger":"controller.gitrepository","msg":"no changes since last reconcilation: observed revision","controllerGroup":"source.toolkit.fluxcd.io","controllerKind":"GitRepository","gitRepository":{"name":"podinfo","namespace":"flux-system"},"name":"flux-system","namespace":"flux-system","reconcileID":"543ef9b2ef-4ea5-47b7-b887-a247cafc1bce"}
{"level":"error","ts":"2022-08-02T12:56:34.961Z","logger":"controller.kustomization","msg":"no changes since last reconcilation: observed revision","reconciler group":"kustomize.toolkit.fluxcd.io","reconciler kind":"Kustomization","name":"flux-system","namespace":"flux-system"}
{"level":"info","ts":"2022-08-02T12:56:34.961Z","logger":"controller.kustomization","msg":"no changes since last reconcilation: observed revision","reconciler group":"kustomize.toolkit.fluxcd.io","reconciler kind":"Kustomization","name":"podinfo","namespace":"default"}
{"level":"info","ts":"2022-08-02T12:56:34.961Z","logger":"controller.gitrepository","msg":"no changes since last reconcilation: observed revision","reconciler group":"source.toolkit.fluxcd.io","reconciler kind":"GitRepository","name":"podinfo","namespace":"default"}
{"level":"error","ts":"2022-08-02T12:56:34.961Z","logger":"controller.kustomization","msg":"no changes since last reconcilation: observed revision","reconciler group":"kustomize.toolkit.fluxcd.io","reconciler kind":"Kustomization","name":"podinfo","namespace":"flux-system"}
`
type testResponseMapper struct {
}
func (t *testResponseMapper) DoRaw(_ context.Context) ([]byte, error) {
return nil, nil
}
func (t *testResponseMapper) Stream(_ context.Context) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader(testPodLogs)), nil
}

View File

@@ -27,12 +27,9 @@ import (
"github.com/spf13/cobra"
"golang.org/x/term"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cli-runtime/pkg/genericclioptions"
_ "k8s.io/client-go/plugin/pkg/client/auth"
runclient "github.com/fluxcd/pkg/runtime/client"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
)
@@ -97,18 +94,6 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`,
# Uninstall Flux and delete CRDs
flux uninstall`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
ns, err := cmd.Flags().GetString("namespace")
if err != nil {
return fmt.Errorf("error getting namespace: %w", err)
}
if e := validation.IsDNS1123Label(ns); len(e) > 0 {
return fmt.Errorf("namespace must be a valid DNS label: %q", ns)
}
return nil
},
}
var logger = stderrLogger{stderr: os.Stderr}
@@ -132,7 +117,6 @@ func (r *RequestError) Error() string {
var rootArgs = NewRootFlags()
var kubeconfigArgs = genericclioptions.NewConfigFlags(false)
var kubeclientOptions = new(runclient.Options)
func init() {
rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 5*time.Minute, "timeout for this operation")
@@ -150,8 +134,6 @@ func init() {
kubeconfigArgs.APIServer = &apiServer
rootCmd.PersistentFlags().StringVar(kubeconfigArgs.APIServer, "server", *kubeconfigArgs.APIServer, "The address and port of the Kubernetes API server")
kubeclientOptions.BindFlags(rootCmd.PersistentFlags())
rootCmd.RegisterFlagCompletionFunc("context", contextsCompletionFunc)
rootCmd.RegisterFlagCompletionFunc("namespace", resourceNamesCompletionFunc(corev1.SchemeGroupVersion.WithKind("Namespace")))
@@ -191,18 +173,17 @@ func configureDefaultNamespace() {
*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace
fromEnv := os.Getenv("FLUX_SYSTEM_NAMESPACE")
if fromEnv != "" {
// namespace must be a valid DNS label. Assess against validation
// used upstream, and ignore invalid values as environment vars
// may not be actively provided by end-user.
if e := validation.IsDNS1123Label(fromEnv); len(e) > 0 {
logger.Warningf(" ignoring invalid FLUX_SYSTEM_NAMESPACE: %q", fromEnv)
return
}
kubeconfigArgs.Namespace = &fromEnv
}
}
func homeDir() string {
if h := os.Getenv("HOME"); h != "" {
return h
}
return os.Getenv("USERPROFILE") // windows
}
// readPasswordFromStdin reads a password from stdin and returns the input
// with trailing newline and/or carriage return removed. It also makes sure that terminal
// echoing is turned off if stdin is a terminal.

View File

@@ -20,7 +20,6 @@ import (
"bufio"
"bytes"
"context"
"flag"
"fmt"
"io"
"os"
@@ -43,9 +42,6 @@ import (
var nextNamespaceId int64
// update allows golden files to be updated based on the current output.
var update = flag.Bool("update", false, "update golden files")
// Return a unique namespace with the specified prefix, for tests to create
// objects that won't collide with each other.
func allocateNamespace(prefix string) string {
@@ -288,38 +284,24 @@ func assertGoldenFile(goldenFile string) assertFunc {
// is pre-processed with the specified templateValues.
func assertGoldenTemplateFile(goldenFile string, templateValues map[string]string) assertFunc {
goldenFileContents, fileErr := os.ReadFile(goldenFile)
return assert(
assertSuccess(),
func(output string, err error) error {
if fileErr != nil {
return fmt.Errorf("Error reading golden file '%s': %s", goldenFile, fileErr)
return func(output string, err error) error {
if fileErr != nil {
return fmt.Errorf("Error reading golden file '%s': %s", goldenFile, fileErr)
}
var expectedOutput string
if len(templateValues) > 0 {
expectedOutput, err = executeTemplate(string(goldenFileContents), templateValues)
if err != nil {
return fmt.Errorf("Error executing golden template file '%s': %s", goldenFile, err)
}
var expectedOutput string
if len(templateValues) > 0 {
expectedOutput, err = executeTemplate(string(goldenFileContents), templateValues)
if err != nil {
return fmt.Errorf("Error executing golden template file '%s': %s", goldenFile, err)
}
} else {
expectedOutput = string(goldenFileContents)
}
if assertErr := assertGoldenValue(expectedOutput)(output, err); assertErr != nil {
// Update the golden files if comparison fails and the update flag is set.
if *update && output != "" {
// Skip update if there are template values.
if len(templateValues) > 0 {
fmt.Println("NOTE: -update flag passed but golden template files can't be updated, please update it manually")
} else {
if err := os.WriteFile(goldenFile, []byte(output), 0644); err != nil {
return fmt.Errorf("failed to update golden file '%s': %v", goldenFile, err)
}
return nil
}
}
return fmt.Errorf("Mismatch from golden file '%s': %v", goldenFile, assertErr)
}
return nil
})
} else {
expectedOutput = string(goldenFileContents)
}
if assertErr := assertGoldenValue(expectedOutput)(output, err); assertErr != nil {
return fmt.Errorf("Mismatch from golden file '%s': %v", goldenFile, assertErr)
}
return nil
}
}
type TestClusterMode int
@@ -343,6 +325,7 @@ type cmdTestCase struct {
func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
actual, testErr := executeCommand(cmd.args)
// If the cmd error is a change, discard it
if isChangeError(testErr) {
testErr = nil
@@ -384,51 +367,10 @@ func executeCommand(cmd string) (string, error) {
return result, err
}
// resetCmdArgs resets the flags for various cmd
// Note: this will also clear default value of the flags set in init()
func resetCmdArgs() {
*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace
alertArgs = alertFlags{}
alertProviderArgs = alertProviderFlags{}
bootstrapArgs = NewBootstrapFlags()
bServerArgs = bServerFlags{}
buildKsArgs = buildKsFlags{}
checkArgs = checkFlags{}
createArgs = createFlags{}
deleteArgs = deleteFlags{}
diffKsArgs = diffKsFlags{}
exportArgs = exportFlags{}
getArgs = GetFlags{}
gitArgs = gitFlags{}
githubArgs = githubFlags{}
gitlabArgs = gitlabFlags{}
helmReleaseArgs = helmReleaseFlags{
reconcileStrategy: "ChartVersion",
}
imagePolicyArgs = imagePolicyFlags{}
imageRepoArgs = imageRepoFlags{}
imageUpdateArgs = imageUpdateFlags{}
kustomizationArgs = NewKustomizationFlags()
receiverArgs = receiverFlags{}
resumeArgs = ResumeFlags{}
rhrArgs = reconcileHelmReleaseFlags{}
rksArgs = reconcileKsFlags{}
secretGitArgs = NewSecretGitFlags()
secretHelmArgs = secretHelmFlags{}
secretTLSArgs = secretTLSFlags{}
sourceBucketArgs = sourceBucketFlags{}
sourceGitArgs = newSourceGitFlags()
sourceHelmArgs = sourceHelmFlags{}
sourceOCIRepositoryArgs = sourceOCIRepositoryFlags{}
suspendArgs = SuspendFlags{}
tenantArgs = tenantFlags{}
traceArgs = traceFlags{}
treeKsArgs = TreeKsFlags{}
uninstallArgs = uninstallFlags{}
versionArgs = versionFlags{
output: "yaml",
}
}
func isChangeError(err error) bool {

View File

@@ -1,31 +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 (
"github.com/spf13/cobra"
)
var pullCmd = &cobra.Command{
Use: "pull",
Short: "Pull artifacts",
Long: "The pull command is used to download OCI artifacts.",
}
func init() {
rootCmd.AddCommand(pullCmd)
}

View File

@@ -1,119 +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"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/spf13/cobra"
oci "github.com/fluxcd/pkg/oci/client"
)
var pullArtifactCmd = &cobra.Command{
Use: "artifact",
Short: "Pull artifact",
Long: `The pull artifact command downloads and extracts the OCI artifact content to the given path.
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: ` # Pull an OCI artifact created by flux from GHCR
flux pull artifact oci://ghcr.io/org/manifests/app:v0.0.1 --output ./path/to/local/manifests
`,
RunE: pullArtifactCmdRun,
}
type pullArtifactFlags struct {
output string
creds string
provider flags.SourceOCIProvider
}
var pullArtifactArgs = newPullArtifactFlags()
func newPullArtifactFlags() pullArtifactFlags {
return pullArtifactFlags{
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
}
}
func init() {
pullArtifactCmd.Flags().StringVarP(&pullArtifactArgs.output, "output", "o", "", "path where the artifact content should be extracted.")
pullArtifactCmd.Flags().StringVar(&pullArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
pullArtifactCmd.Flags().Var(&pullArtifactArgs.provider, "provider", sourceOCIRepositoryArgs.provider.Description())
pullCmd.AddCommand(pullArtifactCmd)
}
func pullArtifactCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("artifact URL is required")
}
ociURL := args[0]
if pullArtifactArgs.output == "" {
return fmt.Errorf("invalid output path %s", pullArtifactArgs.output)
}
if fs, err := os.Stat(pullArtifactArgs.output); err != nil || !fs.IsDir() {
return fmt.Errorf("invalid output path %s", pullArtifactArgs.output)
}
url, err := oci.ParseArtifactURL(ociURL)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
ociClient := oci.NewLocalClient()
if pullArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pullArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials")
if err := ociClient.LoginWithCredentials(pullArtifactArgs.creds); err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
}
}
if pullArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials")
ociProvider, err := pullArtifactArgs.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)
}
}
logger.Actionf("pulling artifact from %s", url)
meta, err := ociClient.Pull(ctx, url, pullArtifactArgs.output)
if err != nil {
return err
}
logger.Successf("source %s", meta.Source)
logger.Successf("revision %s", meta.Revision)
logger.Successf("digest %s", meta.Digest)
logger.Successf("artifact content extracted to %s", pullArtifactArgs.output)
return nil
}

View File

@@ -1,170 +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"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/spf13/cobra"
oci "github.com/fluxcd/pkg/oci/client"
)
var pushArtifactCmd = &cobra.Command{
Use: "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.
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
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/manifests" \
--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
echo $DOCKER_PAT | docker login --username flux --password-stdin
flux push artifact oci://docker.io/org/app-config:$(git tag --points-at HEAD) \
--path="./path/to/local/manifests" \
--source="$(git config --get remote.origin.url)" \
--revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)"
# Login directly to the registry provider
# You might need to export the following variable if you use local config files for AWS:
# export AWS_SDK_LOAD_CONFIG=1
flux push artifact oci://<account>.dkr.ecr.<region>.amazonaws.com/foo:v1:$(git tag --points-at HEAD) \
--path="./path/to/local/manifests" \
--source="$(git config --get remote.origin.url)" \
--revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)" \
--provider aws
# Or pass credentials directly
flux push artifact oci://docker.io/org/app-config:$(git tag --points-at HEAD) \
--path="./path/to/local/manifests" \
--source="$(git config --get remote.origin.url)" \
--revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)" \
--creds flux:$DOCKER_PAT
`,
RunE: pushArtifactCmdRun,
}
type pushArtifactFlags struct {
path string
source string
revision string
creds string
provider flags.SourceOCIProvider
ignorePaths []string
}
var pushArtifactArgs = newPushArtifactFlags()
func newPushArtifactFlags() pushArtifactFlags {
return pushArtifactFlags{
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
}
}
func init() {
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.path, "path", "", "path to the directory where the Kubernetes manifests are located")
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.source, "source", "", "the source address, e.g. the Git URL")
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.revision, "revision", "", "the source revision in the format '<branch|tag>/<commit-sha>'")
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
pushArtifactCmd.Flags().Var(&pushArtifactArgs.provider, "provider", pushArtifactArgs.provider.Description())
pushArtifactCmd.Flags().StringSliceVar(&pushArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
pushCmd.AddCommand(pushArtifactCmd)
}
func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("artifact URL is required")
}
ociURL := args[0]
if pushArtifactArgs.source == "" {
return fmt.Errorf("--source is required")
}
if pushArtifactArgs.revision == "" {
return fmt.Errorf("--revision is required")
}
if pushArtifactArgs.path == "" {
return fmt.Errorf("invalid path %q", pushArtifactArgs.path)
}
url, err := oci.ParseArtifactURL(ociURL)
if err != nil {
return err
}
if _, err := os.Stat(pushArtifactArgs.path); err != nil {
return fmt.Errorf("invalid path '%s', must point to an existing directory or file", buildArtifactArgs.path)
}
meta := oci.Metadata{
Source: pushArtifactArgs.source,
Revision: pushArtifactArgs.revision,
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
ociClient := oci.NewLocalClient()
if pushArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pushArtifactArgs.creds != "" {
logger.Actionf("logging in to registry with credentials")
if err := ociClient.LoginWithCredentials(pushArtifactArgs.creds); err != nil {
return fmt.Errorf("could not login with credentials: %w", err)
}
}
if pushArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
logger.Actionf("logging in to registry with provider credentials")
ociProvider, err := pushArtifactArgs.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)
}
}
logger.Actionf("pushing artifact to %s", url)
digest, err := ociClient.Push(ctx, url, pushArtifactArgs.path, meta, pushArtifactArgs.ignorePaths)
if err != nil {
return fmt.Errorf("pushing artifact failed: %w", err)
}
logger.Successf("artifact successfully pushed to %s", digest)
return nil
}

View File

@@ -60,22 +60,13 @@ type reconcilable interface {
GetAnnotations() map[string]string
SetAnnotations(map[string]string)
// this is usually implemented by GOTK types, since it's used for meta.SetResourceCondition
GetStatusConditions() *[]metav1.Condition
lastHandledReconcileRequest() string // what was the last handled reconcile request?
successMessage() string // what do you want to tell people when successfully reconciled?
}
func reconcilableConditions(object reconcilable) []metav1.Condition {
if s, ok := object.(meta.ObjectWithConditions); ok {
return s.GetConditions()
}
if s, ok := object.(oldConditions); ok {
return *s.GetStatusConditions()
}
return []metav1.Condition{}
}
func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("%s name is required", reconcile.kind)
@@ -85,7 +76,7 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}
@@ -127,7 +118,7 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
return err
}
readyCond := apimeta.FindStatusCondition(reconcilableConditions(reconcile.object), meta.ReadyCondition)
readyCond := apimeta.FindStatusCondition(*reconcile.object.GetStatusConditions(), meta.ReadyCondition)
if readyCond == nil {
return fmt.Errorf("status can't be determined")
}
@@ -146,7 +137,7 @@ func reconciliationHandled(ctx context.Context, kubeClient client.Client,
if err != nil {
return false, err
}
isProgressing := apimeta.IsStatusConditionPresentAndEqual(reconcilableConditions(obj),
isProgressing := apimeta.IsStatusConditionPresentAndEqual(*obj.GetStatusConditions(),
meta.ReadyCondition, metav1.ConditionUnknown)
return obj.lastHandledReconcileRequest() != lastHandledReconcileAt && !isProgressing, nil
}
@@ -183,7 +174,7 @@ func isReconcileReady(ctx context.Context, kubeClient client.Client,
return false, err
}
if c := apimeta.FindStatusCondition(reconcilableConditions(obj), meta.ReadyCondition); c != nil {
if c := apimeta.FindStatusCondition(*obj.GetStatusConditions(), meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil

View File

@@ -54,7 +54,7 @@ func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -21,7 +21,7 @@ import (
"k8s.io/apimachinery/pkg/types"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var reconcileHrCmd = &cobra.Command{

View File

@@ -21,7 +21,7 @@ import (
"k8s.io/apimachinery/pkg/types"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var reconcileKsCmd = &cobra.Command{
@@ -65,11 +65,6 @@ func (obj kustomizationAdapter) reconcileSource() bool {
func (obj kustomizationAdapter) getSource() (reconcileCommand, types.NamespacedName) {
var cmd reconcileCommand
switch obj.Spec.SourceRef.Kind {
case sourcev1.OCIRepositoryKind:
cmd = reconcileCommand{
apiType: ociRepositoryType,
object: ociRepositoryAdapter{&sourcev1.OCIRepository{}},
}
case sourcev1.GitRepositoryKind:
cmd = reconcileCommand{
apiType: gitRepositoryType,

View File

@@ -54,7 +54,7 @@ func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}

View File

@@ -17,11 +17,18 @@ limitations under the License.
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var reconcileSourceBucketCmd = &cobra.Command{
@@ -41,6 +48,31 @@ func init() {
reconcileSourceCmd.AddCommand(reconcileSourceBucketCmd)
}
func isBucketReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, bucket)
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if bucket.Generation != bucket.Status.ObservedGeneration {
return false, nil
}
if c := apimeta.FindStatusCondition(bucket.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}
func (obj bucketAdapter) lastHandledReconcileRequest() string {
return obj.Status.GetLastHandledReconcileRequest()
}

View File

@@ -21,7 +21,7 @@ import (
"github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
var reconcileSourceGitCmd = &cobra.Command{

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