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

Compare commits

..

4 Commits

Author SHA1 Message Date
Stefan Prodan
e0bc754ad0 Refer to authorisation model in RFC-0001
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-12-17 11:58:45 +02:00
Michael Bridgen
a67d19317b Explain authorisation model and mechanisms
The multi-tenancy implementations described rely on impersonation and
remote apply; to make this RFC stand by itself, those need to be
explained, along with the authorisation model (how Flux "decides" what
it's allowed to do).

This commit adds a summary of the authorisation model, impersonation,
and remote apply, and rejigs the headings a little to make space.

Signed-off-by: Michael Bridgen <michael@weave.works>
2021-12-17 11:58:39 +02:00
Stefan Prodan
dc7cb189fc Incorporate Michael's suggestions
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-12-17 11:58:39 +02:00
Stefan Prodan
d23d87ac94 Define Flux tenancy models
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-12-17 11:58:38 +02:00
145 changed files with 1107 additions and 3713 deletions

View File

@@ -12,7 +12,7 @@ provides=("flux-bin")
conflicts=("flux-bin")
replaces=("flux-cli")
depends=("glibc")
makedepends=('go>=1.17', 'kustomize>=3.0')
makedepends=('go>=1.16', 'kustomize>=3.0')
optdepends=('bash-completion: auto-completion for flux in Bash',
'zsh-completions: auto-completion for flux in ZSH')
source=(
@@ -30,20 +30,12 @@ build() {
export CGO_CXXFLAGS="$CXXFLAGS"
export CGO_CPPFLAGS="$CPPFLAGS"
export GOFLAGS="-buildmode=pie -trimpath -mod=readonly -modcacherw"
make cmd/flux/.manifests.done
./manifests/scripts/bundle.sh "${PWD}/manifests" "${PWD}/cmd/flux/manifests"
go build -ldflags "-linkmode=external -X main.VERSION=${pkgver}" -o ${_srcname} ./cmd/flux
}
check() {
cd "flux2-${pkgver}"
case $CARCH in
aarch64)
export ENVTEST_ARCH=arm64
;;
armv6h|armv7h)
export ENVTEST_ARCH=arm
;;
esac
make test
}

View File

@@ -11,7 +11,7 @@ license=("APACHE")
provides=("flux-bin")
conflicts=("flux-bin")
depends=("glibc")
makedepends=('go>=1.17', 'kustomize>=3.0', 'git')
makedepends=('go>=1.16', 'kustomize>=3.0')
optdepends=('bash-completion: auto-completion for flux in Bash',
'zsh-completions: auto-completion for flux in ZSH')
source=(
@@ -32,20 +32,12 @@ build() {
export CGO_CXXFLAGS="$CXXFLAGS"
export CGO_CPPFLAGS="$CPPFLAGS"
export GOFLAGS="-buildmode=pie -trimpath -mod=readonly -modcacherw"
make cmd/flux/.manifests.done
make cmd/flux/manifests
go build -ldflags "-linkmode=external -X main.VERSION=${pkgver}" -o ${_srcname} ./cmd/flux
}
check() {
cd "flux2"
case $CARCH in
aarch64)
export ENVTEST_ARCH=arm64
;;
armv6h|armv7h)
export ENVTEST_ARCH=arm
;;
esac
make test
}

View File

@@ -21,9 +21,8 @@ set -eu
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"
PACKAGES="apt-transport-https ca-certificates software-properties-common build-essential libssl-dev gnupg lsb-release jq"
# install prerequisites
apt-get update \
@@ -53,12 +52,6 @@ curl -Lo ./kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/release
&& rm kustomize.tar.gz
install -o root -g root -m 0755 kustomize /usr/local/bin/kustomize
# install helm
curl -Lo ./helm.tar.gz https://get.helm.sh/helm-v${HELM_VERSION}-linux-arm64.tar.gz \
&& tar -zxvf helm.tar.gz \
&& rm helm.tar.gz
install -o root -g root -m 0755 linux-arm64/helm /usr/local/bin/helm
# download runner
curl -o actions-runner-linux-arm64.tar.gz -L https://github.com/actions/runner/releases/download/v${GITHUB_RUNNER_VERSION}/actions-runner-linux-arm64-${GITHUB_RUNNER_VERSION}.tar.gz \
&& tar xzf actions-runner-linux-arm64.tar.gz \

View File

@@ -17,13 +17,13 @@ jobs:
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.17-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go1.17-
${{ runner.os }}-go1.16-
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.17.x
go-version: 1.16.x
- name: Setup Kubernetes
uses: engineerd/setup-kind@v0.5.0
with:
@@ -103,26 +103,13 @@ jobs:
/tmp/flux reconcile image repository podinfo
/tmp/flux reconcile image update flux-system
/tmp/flux get images all
retries=10
count=0
ok=false
until ${ok}; do
/tmp/flux get image update flux-system | grep 'commit' && ok=true || ok=false
count=$(($count + 1))
if [[ ${count} -eq ${retries} ]]; then
echo "No more retries left"
exit 1
fi
sleep 6
/tmp/flux reconcile image update flux-system
done
/tmp/flux get images policy podinfo | grep "5.2.1"
/tmp/flux get image update flux-system | grep commit
env:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
GITHUB_REPO_NAME: ${{ steps.vars.outputs.test_repo_name }}
GITHUB_ORG_NAME: fluxcd-testing
- name: delete repository
if: ${{ always() }}
run: |
curl \
-X DELETE \

View File

@@ -16,7 +16,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.17.x
go-version: 1.16.x
- name: Prepare
id: prep
run: |

View File

@@ -17,13 +17,13 @@ jobs:
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.17-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go1.17-
${{ runner.os }}-go1.16-
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.17.x
go-version: 1.16.x
- name: Install libgit2
run: |
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 648ACFD622F3D138

View File

@@ -16,19 +16,23 @@ jobs:
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.17-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go1.17-
${{ runner.os }}-go1.16-
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.17.x
go-version: 1.16.x
- name: Setup Kubernetes
uses: engineerd/setup-kind@v0.5.0
with:
version: v0.11.1
image: kindest/node:v1.20.7
image: kindest/node:v1.19.11@sha256:07db187ae84b4b7de440a73886f008cf903fcf5764ba8106a9fd5243d6f32729
config: .github/kind/config.yaml # disable KIND-net
- name: Setup envtest
uses: fluxcd/pkg/actions/envtest@main
with:
version: "1.21.x"
- name: Setup Calico for network policy
run: |
kubectl apply -f https://docs.projectcalico.org/v3.20/manifests/calico.yaml

View File

@@ -4,11 +4,6 @@ on:
push:
tags: [ 'v*' ]
permissions:
contents: write # needed to write releases
id-token: write # needed for keyless signing
packages: write # needed for ghcr access
jobs:
goreleaser:
runs-on: ubuntu-latest
@@ -20,18 +15,16 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.17.x
go-version: 1.16.x
- name: Setup QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Setup Syft
uses: anchore/sbom-action/download-syft@v0
- name: Setup Cosign
uses: sigstore/cosign-installer@main
- name: Setup Kustomize
uses: fluxcd/pkg//actions/kustomize@main
with:
buildkitd-flags: "--debug"
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
@@ -43,6 +36,18 @@ jobs:
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
- name: Download release notes utility
env:
GH_REL_URL: https://github.com/buchanae/github-release-notes/releases/download/0.2.0/github-release-notes-linux-amd64-0.2.0.tar.gz
run: cd /tmp && curl -sSL ${GH_REL_URL} | tar xz && sudo mv github-release-notes /usr/local/bin/
- name: Generate release notes
run: |
echo 'CHANGELOG' > /tmp/release.txt
github-release-notes -org fluxcd -repo toolkit -since-latest-release -include-author >> /tmp/release.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Kustomize
uses: fluxcd/pkg//actions/kustomize@main
- name: Generate manifests
run: |
make cmd/flux/.manifests.done
@@ -61,22 +66,11 @@ jobs:
- name: Archive the OpenAPI JSON schemas
run: |
tar -czvf ./output/crd-schemas.tar.gz -C schemas .
- name: Download release notes utility
env:
GH_REL_URL: https://github.com/buchanae/github-release-notes/releases/download/0.2.0/github-release-notes-linux-amd64-0.2.0.tar.gz
run: cd /tmp && curl -sSL ${GH_REL_URL} | tar xz && sudo mv github-release-notes /usr/local/bin/
- name: Generate release notes
run: |
NOTES="./output/notes.md"
echo '## CLI Changelog' > ${NOTES}
github-release-notes -org fluxcd -repo flux2 -since-latest-release -include-author >> ${NOTES}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v1
with:
version: latest
args: release --release-notes=output/notes.md --skip-validate
args: release --release-notes=/tmp/release.txt --skip-validate
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}

View File

@@ -16,7 +16,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.17.x
go-version: 1.16.x
- name: Update component versions
id: update
run: |

1
.gitignore vendored
View File

@@ -20,7 +20,6 @@ bin/
output/
cmd/flux/manifests/
cmd/flux/.manifests.done
testbin/
# Docs
site/

View File

@@ -40,36 +40,6 @@ archives:
format: zip
files:
- none*
source:
enabled: true
name_template: '{{ .ProjectName }}_{{ .Version }}_source_code'
sboms:
- id: source
artifacts: source
documents:
- "{{ .ProjectName }}_{{ .Version }}_sbom.spdx.json"
release:
extra_files:
- glob: output/crd-schemas.tar.gz
- glob: output/manifests.tar.gz
- glob: output/install.yaml
checksum:
extra_files:
- glob: output/crd-schemas.tar.gz
- glob: output/manifests.tar.gz
- glob: output/install.yaml
signs:
- cmd: cosign
env:
- COSIGN_EXPERIMENTAL=1
certificate: '${artifact}.pem'
args:
- sign-blob
- '--output-certificate=${certificate}'
- '--output-signature=${signature}'
- '${artifact}'
artifacts: checksum
output: true
brews:
- name: flux
tap:
@@ -108,12 +78,17 @@ publishers:
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
cmd: |
.github/aur/flux-go/publish.sh {{ .Version }}
release:
extra_files:
- glob: ./output/crd-schemas.tar.gz
- glob: ./output/manifests.tar.gz
- glob: ./output/install.yaml
dockers:
- image_templates:
- 'fluxcd/flux-cli:{{ .Tag }}-amd64'
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-amd64'
dockerfile: Dockerfile
use: buildx
use_buildx: true
goos: linux
goarch: amd64
build_flag_templates:
@@ -129,7 +104,7 @@ dockers:
- 'fluxcd/flux-cli:{{ .Tag }}-arm64'
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm64'
dockerfile: Dockerfile
use: buildx
use_buildx: true
goos: linux
goarch: arm64
build_flag_templates:
@@ -145,7 +120,7 @@ dockers:
- 'fluxcd/flux-cli:{{ .Tag }}-arm'
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm'
dockerfile: Dockerfile
use: buildx
use_buildx: true
goos: linux
goarch: arm
goarm: 7
@@ -169,12 +144,3 @@ docker_manifests:
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-amd64'
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm64'
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm'
docker_signs:
- cmd: cosign
env:
- COSIGN_EXPERIMENTAL=1
args:
- sign
- '${artifact}'
artifacts: all
output: true

View File

@@ -67,9 +67,9 @@ for source changes.
Prerequisites:
* go >= 1.17
* kubectl >= 1.20
* kustomize >= 4.4
* go >= 1.16
* kubectl >= 1.19
* kustomize >= 4.0
Install the [controller-runtime/envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest) binaries with:

View File

@@ -1,15 +1,15 @@
FROM alpine:3.15 as builder
FROM alpine:3.14 as builder
RUN apk add --no-cache ca-certificates curl
ARG ARCH=linux/amd64
ARG KUBECTL_VER=1.23.1
ARG KUBECTL_VER=1.22.2
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.15 as flux-cli
FROM alpine:3.14 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
@@ -20,5 +20,4 @@ RUN apk add --no-cache ca-certificates
COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/
COPY --chmod=755 flux /usr/local/bin/
USER 65534:65534
ENTRYPOINT [ "flux" ]

View File

@@ -1,8 +1,8 @@
VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | head -n 1 | tr -d '"')
EMBEDDED_MANIFESTS_TARGET=cmd/flux/.manifests.done
TEST_KUBECONFIG?=/tmp/flux-e2e-test-kubeconfig
# Architecture to use envtest with
ENVTEST_ARCH ?= amd64
ENVTEST_BIN_VERSION?=latest
KUBEBUILDER_ASSETS?=$(shell $(SETUP_ENVTEST) use -i $(ENVTEST_BIN_VERSION) -p path)
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
@@ -17,7 +17,6 @@ all: test build
tidy:
go mod tidy
cd tests/azure && go mod tidy
fmt:
go fmt ./...
@@ -34,7 +33,6 @@ cleanup-kind:
kind delete cluster --name=flux-e2e-test
rm $(TEST_KUBECONFIG)
KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)"
test: $(EMBEDDED_MANIFESTS_TARGET) tidy fmt vet install-envtest
KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test ./... -coverprofile cover.out --tags=unit
@@ -60,33 +58,27 @@ install:
install-dev:
CGO_ENABLED=0 go build -o /usr/local/bin ./cmd/flux
install-envtest: setup-envtest
$(SETUP_ENVTEST) use $(ENVTEST_BIN_VERSION)
setup-bootstrap-patch:
go run ./tests/bootstrap/main.go
setup-image-automation:
cd tests/image-automation && go run main.go
ENVTEST_ASSETS_DIR=$(shell pwd)/testbin
ENVTEST_KUBERNETES_VERSION?=latest
install-envtest: setup-envtest
mkdir -p ${ENVTEST_ASSETS_DIR}
$(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR)
ENVTEST = $(shell pwd)/bin/setup-envtest
.PHONY: envtest
setup-envtest: ## Download envtest-setup locally if necessary.
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)
# go-install-tool will 'go install' any package $2 and install it to $1.
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
define go-install-tool
@[ -f $(1) ] || { \
set -e ;\
TMP_DIR=$$(mktemp -d) ;\
cd $$TMP_DIR ;\
go mod init tmp ;\
echo "Downloading $(2)" ;\
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
rm -rf $$TMP_DIR ;\
}
endef
# Find or download setup-envtest
setup-envtest:
ifeq (, $(shell which setup-envtest))
@{ \
set -e ;\
SETUP_ENVTEST_TMP_DIR=$$(mktemp -d) ;\
cd $$SETUP_ENVTEST_TMP_DIR ;\
go mod init tmp ;\
go get sigs.k8s.io/controller-runtime/tools/setup-envtest@latest ;\
rm -rf $$SETUP_ENVTEST_TMP_DIR ;\
}
SETUP_ENVTEST=$(GOBIN)/setup-envtest
else
SETUP_ENVTEST=$(shell which setup-envtest)
endif

View File

@@ -12,9 +12,6 @@ inputs:
description: "arch can be amd64, arm64 or arm"
required: true
default: "amd64"
bindir:
description: "Optional location of the Flux binary. Will not use sudo if set. Updates System Path."
required: false
runs:
using: composite
steps:
@@ -32,16 +29,10 @@ runs:
curl -sL ${BIN_URL} -o /tmp/flux.tar.gz
mkdir -p /tmp/flux
tar -C /tmp/flux/ -zxvf /tmp/flux.tar.gz
- name: "Copy Flux binary to execute location"
- name: "Add flux binary to /usr/local/bin"
shell: bash
run: |
BINDIR=${{ inputs.bindir }}
if [ -z $BINDIR ]; then
sudo cp /tmp/flux/flux /usr/local/bin
else
cp /tmp/flux/flux "${BINDIR}"
echo "${BINDIR}" >> $GITHUB_PATH
fi
sudo cp /tmp/flux/flux /usr/local/bin
- name: "Cleanup tmp"
shell: bash
run: |

View File

@@ -121,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)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
@@ -179,7 +179,7 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
installOptions := install.Options{
BaseURL: rootArgs.defaults.BaseURL,
Version: bootstrapArgs.version,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Components: bootstrapComponents(),
Registry: bootstrapArgs.registry,
ImagePullSecret: bootstrapArgs.imagePullSecret,
@@ -200,7 +200,7 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
// Source generation and secret config
secretOpts := sourcesecret.Options{
Name: bootstrapArgs.secretName,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
TargetPath: bServerArgs.path.String(),
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
}
@@ -232,8 +232,8 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
// Sync manifest config
syncOpts := sync.Options{
Interval: bServerArgs.interval,
Name: *kubeconfigArgs.Namespace,
Namespace: *kubeconfigArgs.Namespace,
Name: rootArgs.namespace,
Namespace: rootArgs.namespace,
Branch: bootstrapArgs.branch,
Secret: bootstrapArgs.secretName,
TargetPath: bServerArgs.path.ToSlash(),
@@ -251,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),
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
bootstrap.WithLogger(logger),
bootstrap.WithCABundle(caBundle),
}

View File

@@ -101,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)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
@@ -128,7 +128,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
installOptions := install.Options{
BaseURL: rootArgs.defaults.BaseURL,
Version: bootstrapArgs.version,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Components: bootstrapComponents(),
Registry: bootstrapArgs.registry,
ImagePullSecret: bootstrapArgs.imagePullSecret,
@@ -149,7 +149,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
// Source generation and secret config
secretOpts := sourcesecret.Options{
Name: bootstrapArgs.secretName,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
TargetPath: gitArgs.path.String(),
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
}
@@ -161,15 +161,10 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
secretOpts.CAFilePath = bootstrapArgs.caFile
}
// Remove port of the given host when not syncing over HTTP/S to not assume port for protocol
// This _might_ be overwritten later on by e.g. --ssh-hostname
if repositoryURL.Scheme != "https" && repositoryURL.Scheme != "http" {
repositoryURL.Host = repositoryURL.Hostname()
}
// Configure repository URL to match auth config for sync.
repositoryURL.User = nil
repositoryURL.Scheme = "https"
repositoryURL.Host = repositoryURL.Hostname()
} else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.Password = gitArgs.password
@@ -199,8 +194,8 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
// Sync manifest config
syncOpts := sync.Options{
Interval: gitArgs.interval,
Name: *kubeconfigArgs.Namespace,
Namespace: *kubeconfigArgs.Namespace,
Name: rootArgs.namespace,
Namespace: rootArgs.namespace,
URL: repositoryURL.String(),
Branch: bootstrapArgs.branch,
Secret: bootstrapArgs.secretName,
@@ -225,7 +220,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),
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
bootstrap.WithLogger(logger),
bootstrap.WithCABundle(caBundle),

View File

@@ -125,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)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
@@ -175,7 +175,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
installOptions := install.Options{
BaseURL: rootArgs.defaults.BaseURL,
Version: bootstrapArgs.version,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Components: bootstrapComponents(),
Registry: bootstrapArgs.registry,
ImagePullSecret: bootstrapArgs.imagePullSecret,
@@ -196,7 +196,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
// Source generation and secret config
secretOpts := sourcesecret.Options{
Name: bootstrapArgs.secretName,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
TargetPath: githubArgs.path.ToSlash(),
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
}
@@ -221,8 +221,8 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
// Sync manifest config
syncOpts := sync.Options{
Interval: githubArgs.interval,
Name: *kubeconfigArgs.Namespace,
Namespace: *kubeconfigArgs.Namespace,
Name: rootArgs.namespace,
Namespace: rootArgs.namespace,
Branch: bootstrapArgs.branch,
Secret: bootstrapArgs.secretName,
TargetPath: githubArgs.path.ToSlash(),
@@ -240,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),
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
bootstrap.WithLogger(logger),
bootstrap.WithCABundle(caBundle),
}

View File

@@ -129,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)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
@@ -186,7 +186,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
installOptions := install.Options{
BaseURL: rootArgs.defaults.BaseURL,
Version: bootstrapArgs.version,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Components: bootstrapComponents(),
Registry: bootstrapArgs.registry,
ImagePullSecret: bootstrapArgs.imagePullSecret,
@@ -207,7 +207,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
// Source generation and secret config
secretOpts := sourcesecret.Options{
Name: bootstrapArgs.secretName,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
TargetPath: gitlabArgs.path.String(),
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
}
@@ -235,8 +235,8 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
// Sync manifest config
syncOpts := sync.Options{
Interval: gitlabArgs.interval,
Name: *kubeconfigArgs.Namespace,
Namespace: *kubeconfigArgs.Namespace,
Name: rootArgs.namespace,
Namespace: rootArgs.namespace,
Branch: bootstrapArgs.branch,
Secret: bootstrapArgs.secretName,
TargetPath: gitlabArgs.path.ToSlash(),
@@ -254,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),
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
bootstrap.WithLogger(logger),
bootstrap.WithCABundle(caBundle),
}

View File

@@ -1,31 +0,0 @@
/*
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 (
"github.com/spf13/cobra"
)
var buildCmd = &cobra.Command{
Use: "build",
Short: "Build a flux resource",
Long: "The build command is used to build flux resources.",
}
func init() {
rootCmd.AddCommand(buildCmd)
}

View File

@@ -1,100 +0,0 @@
/*
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 (
"fmt"
"os"
"os/signal"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/internal/build"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
)
var buildKsCmd = &cobra.Command{
Use: "kustomization",
Aliases: []string{"ks"},
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.`,
Example: `# Build the local manifests as they were built on the cluster
flux build kustomization my-app --path ./path/to/local/manifests`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: buildKsCmdRun,
}
type buildKsFlags struct {
path string
}
var buildKsArgs buildKsFlags
func init() {
buildKsCmd.Flags().StringVar(&buildKsArgs.path, "path", "", "Path to the manifests location.)")
buildCmd.AddCommand(buildKsCmd)
}
func buildKsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("%s name is required", kustomizationType.humanKind)
}
name := args[0]
if buildKsArgs.path == "" {
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
}
if fs, err := os.Stat(buildKsArgs.path); err != nil || !fs.IsDir() {
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
}
builder, err := build.NewBuilder(kubeconfigArgs, name, buildKsArgs.path, build.WithTimeout(rootArgs.timeout))
if err != nil {
return err
}
// create a signal channel
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt)
errChan := make(chan error)
go func() {
manifests, err := builder.Build()
if err != nil {
errChan <- err
}
cmd.Print(string(manifests))
errChan <- nil
}()
select {
case <-sigc:
fmt.Println("Build cancelled... exiting.")
return builder.Cancel()
case err := <-errChan:
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,83 +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 (
"testing"
)
func setup(t *testing.T, tmpl map[string]string) {
t.Helper()
testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-source.yaml", tmpl, t)
testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-kustomization.yaml", tmpl, t)
}
func TestBuildKustomization(t *testing.T) {
tests := []struct {
name string
args string
resultFile string
assertFunc string
}{
{
name: "no args",
args: "build kustomization podinfo",
resultFile: "invalid resource path \"\"",
assertFunc: "assertError",
},
{
name: "build podinfo",
args: "build kustomization podinfo --path ./testdata/build-kustomization/podinfo",
resultFile: "./testdata/build-kustomization/podinfo-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build podinfo without service",
args: "build kustomization podinfo --path ./testdata/build-kustomization/delete-service",
resultFile: "./testdata/build-kustomization/podinfo-without-service-result.yaml",
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

@@ -56,7 +56,10 @@ type checkFlags struct {
}
var kubernetesConstraints = []string{
">=1.20.6-0",
">=1.19.0-0",
">=1.16.11-0 <=1.16.15-0",
">=1.17.7-0 <=1.17.17-0",
">=1.18.4-0 <=1.18.20-0",
}
var checkArgs checkFlags
@@ -125,7 +128,7 @@ func fluxCheck() {
}
func kubernetesCheck(constraints []string) bool {
cfg, err := utils.KubeConfig(kubeconfigArgs)
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
return false
@@ -173,7 +176,7 @@ func componentsCheck() bool {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeConfig, err := utils.KubeConfig(kubeconfigArgs)
kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return false
}
@@ -183,7 +186,7 @@ func componentsCheck() bool {
return false
}
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return false
}
@@ -191,7 +194,7 @@ func componentsCheck() bool {
ok := true
selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue}
var list v1.DeploymentList
if err := kubeClient.List(ctx, &list, client.InNamespace(*kubeconfigArgs.Namespace), selector); err == nil {
if err := kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace), selector); err == nil {
for _, d := range list.Items {
if ref, err := buildComponentObjectRefs(d.Name); err == nil {
if err := statusChecker.Assess(ref...); err != nil {

View File

@@ -1,4 +1,3 @@
//go:build e2e
// +build e2e
/*
@@ -30,7 +29,7 @@ import (
)
func TestCheckPre(t *testing.T) {
jsonOutput, err := utils.ExecKubectlCommand(context.TODO(), utils.ModeCapture, *kubeconfigArgs.KubeConfig, *kubeconfigArgs.Context, "version", "--output", "json")
jsonOutput, err := utils.ExecKubectlCommand(context.TODO(), utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, "version", "--output", "json")
if err != nil {
t.Fatalf("Error running utils.ExecKubectlCommand: %v", err.Error())
}

View File

@@ -25,7 +25,10 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
memory "k8s.io/client-go/discovery/cached"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/restmapper"
)
var completionCmd = &cobra.Command{
@@ -39,7 +42,7 @@ func init() {
}
func contextsCompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
rawConfig, err := kubeconfigArgs.ToRawKubeConfigLoader().RawConfig()
rawConfig, err := utils.ClientConfig(rootArgs.kubeconfig, rootArgs.kubecontext).RawConfig()
if err != nil {
return completionError(err)
}
@@ -60,15 +63,16 @@ func resourceNamesCompletionFunc(gvk schema.GroupVersionKind) func(cmd *cobra.Co
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
cfg, err := utils.KubeConfig(kubeconfigArgs)
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return completionError(err)
}
mapper, err := kubeconfigArgs.ToRESTMapper()
dc, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
return completionError(err)
}
mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(dc))
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
@@ -82,7 +86,7 @@ func resourceNamesCompletionFunc(gvk schema.GroupVersionKind) func(cmd *cobra.Co
var dr dynamic.ResourceInterface
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
dr = client.Resource(mapping.Resource).Namespace(*kubeconfigArgs.Namespace)
dr = client.Resource(mapping.Resource).Namespace(rootArgs.namespace)
} else {
dr = client.Resource(mapping.Resource)
}

View File

@@ -104,7 +104,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) // NB globals
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) // NB globals
if err != nil {
return err
}

View File

@@ -102,7 +102,7 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
alert := notificationv1.Alert{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: sourceLabels,
},
Spec: notificationv1.AlertSpec{
@@ -122,7 +122,7 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}

View File

@@ -94,7 +94,7 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
provider := notificationv1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: sourceLabels,
},
Spec: notificationv1.ProviderSpec{
@@ -118,7 +118,7 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}

View File

@@ -160,7 +160,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
helmRelease := helmv2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: sourceLabels,
},
Spec: helmv2.HelmReleaseSpec{
@@ -250,7 +250,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}

View File

@@ -101,7 +101,7 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
var policy = imagev1.ImagePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: objectName,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: labels,
},
Spec: imagev1.ImagePolicySpec{

View File

@@ -104,7 +104,7 @@ func createImageRepositoryRun(cmd *cobra.Command, args []string) error {
var repo = imagev1.ImageRepository{
ObjectMeta: metav1.ObjectMeta{
Name: objectName,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: labels,
},
Spec: imagev1.ImageRepositorySpec{

View File

@@ -108,11 +108,11 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
var update = autov1.ImageUpdateAutomation{
ObjectMeta: metav1.ObjectMeta{
Name: objectName,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: labels,
},
Spec: autov1.ImageUpdateAutomationSpec{
SourceRef: autov1.CrossNamespaceSourceReference{
SourceRef: autov1.SourceReference{
Kind: sourcev1.GitRepositoryKind,
Name: imageUpdateArgs.gitRepoRef,
},

View File

@@ -143,7 +143,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
kustomization := kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: kslabels,
},
Spec: kustomizev1.KustomizationSpec{
@@ -232,7 +232,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}

View File

@@ -109,7 +109,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
receiver := notificationv1.Receiver{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: sourceLabels,
},
Spec: notificationv1.ReceiverSpec{
@@ -130,7 +130,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}

View File

@@ -132,7 +132,7 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
opts := sourcesecret.Options{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: labels,
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
}
@@ -176,14 +176,14 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
if err := upsertSecret(ctx, kubeClient, s); err != nil {
return err
}
logger.Actionf("git secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace)
logger.Actionf("git secret '%s' created in '%s' namespace", name, rootArgs.namespace)
return nil
}

View File

@@ -80,7 +80,7 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
opts := sourcesecret.Options{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: labels,
Username: secretHelmArgs.username,
Password: secretHelmArgs.password,
@@ -100,7 +100,7 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
@@ -112,6 +112,6 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
return err
}
logger.Actionf("helm secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace)
logger.Actionf("helm secret '%s' created in '%s' namespace", name, rootArgs.namespace)
return nil
}

View File

@@ -79,7 +79,7 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
opts := sourcesecret.Options{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: labels,
CAFilePath: secretTLSArgs.caFile,
CertFilePath: secretTLSArgs.certFile,
@@ -97,7 +97,7 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
@@ -109,6 +109,6 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
return err
}
logger.Actionf("tls secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace)
logger.Actionf("tls secret '%s' created in '%s' namespace", name, rootArgs.namespace)
return nil
}

View File

@@ -120,7 +120,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
bucket := &sourcev1.Bucket{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: sourceLabels,
},
Spec: sourcev1.BucketSpec{
@@ -152,7 +152,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
@@ -165,7 +165,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: sourceLabels,
},
StringData: map[string]string{},

View File

@@ -193,7 +193,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
gitRepository := sourcev1.GitRepository{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: sourceLabels,
},
Spec: sourcev1.GitRepositorySpec{
@@ -235,7 +235,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
@@ -244,7 +244,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if sourceGitArgs.secretRef == "" {
secretOpts := sourcesecret.Options{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
}
switch u.Scheme {

View File

@@ -1,4 +1,3 @@
//go:build unit
// +build unit
/*

View File

@@ -118,7 +118,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
helmRepository := &sourcev1.HelmRepository{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Labels: sourceLabels,
},
Spec: sourcev1.HelmRepositorySpec{
@@ -147,7 +147,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
@@ -157,7 +157,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
secretName := fmt.Sprintf("helm-%s", name)
secretOpts := sourcesecret.Options{
Name: secretName,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Username: sourceHelmArgs.username,
Password: sourceHelmArgs.password,
CertFilePath: sourceHelmArgs.certFile,

View File

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

View File

@@ -60,13 +60,13 @@ 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)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Name: name,
}
@@ -85,7 +85,7 @@ func (del deleteCommand) run(cmd *cobra.Command, args []string) error {
}
}
logger.Actionf("deleting %s %s in %s namespace", del.humanKind, name, *kubeconfigArgs.Namespace)
logger.Actionf("deleting %s %s in %s namespace", del.humanKind, name, rootArgs.namespace)
err = kubeClient.Delete(ctx, del.object.asClientObject())
if err != nil {
return err

View File

@@ -1,31 +0,0 @@
/*
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 (
"github.com/spf13/cobra"
)
var diffCmd = &cobra.Command{
Use: "diff",
Short: "Diff a flux resource",
Long: "The diff command is used to do a server-side dry-run on flux resources, then output the diff.",
}
func init() {
rootCmd.AddCommand(diffCmd)
}

View File

@@ -1,98 +0,0 @@
/*
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 (
"fmt"
"os"
"os/signal"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/internal/build"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
)
var diffKsCmd = &cobra.Command{
Use: "kustomization",
Aliases: []string{"ks"},
Short: "Diff Kustomization",
Long: `The diff command does a build, then it performs a server-side dry-run and output the diff.`,
Example: `# Preview changes local changes as they were applied on the cluster
flux diff kustomization my-app --path ./path/to/local/manifests`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: diffKsCmdRun,
}
type diffKsFlags struct {
path string
}
var diffKsArgs diffKsFlags
func init() {
diffKsCmd.Flags().StringVar(&diffKsArgs.path, "path", "", "Path to a local directory that matches the specified Kustomization.spec.path.)")
diffCmd.AddCommand(diffKsCmd)
}
func diffKsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("%s name is required", kustomizationType.humanKind)
}
name := args[0]
if diffKsArgs.path == "" {
return fmt.Errorf("invalid resource path %q", diffKsArgs.path)
}
if fs, err := os.Stat(diffKsArgs.path); err != nil || !fs.IsDir() {
return fmt.Errorf("invalid resource path %q", diffKsArgs.path)
}
builder, err := build.NewBuilder(kubeconfigArgs, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout))
if err != nil {
return err
}
// create a signal channel
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt)
errChan := make(chan error)
go func() {
output, err := builder.Diff()
if err != nil {
errChan <- err
}
cmd.Print(output)
errChan <- nil
}()
select {
case <-sigc:
fmt.Println("Build cancelled... exiting.")
return builder.Cancel()
case err := <-errChan:
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,129 +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"
"os"
"strings"
"testing"
"github.com/fluxcd/flux2/internal/build"
"github.com/fluxcd/pkg/ssa"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestDiffKustomization(t *testing.T) {
tests := []struct {
name string
args string
objectFile string
assert assertFunc
}{
{
name: "no args",
args: "diff kustomization podinfo",
objectFile: "",
assert: assertError("invalid resource path \"\""),
},
{
name: "diff nothing deployed",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo",
objectFile: "",
assert: assertGoldenFile("./testdata/diff-kustomization/nothing-is-deployed.golden"),
},
{
name: "diff with a deployment object",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo",
objectFile: "./testdata/diff-kustomization/deployment.yaml",
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-deployment.golden"),
},
{
name: "diff with a drifted service object",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo",
objectFile: "./testdata/diff-kustomization/service.yaml",
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-service.golden"),
},
{
name: "diff with a drifted secret object",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo",
objectFile: "./testdata/diff-kustomization/secret.yaml",
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-secret.golden"),
},
{
name: "diff with a drifted key in sops secret object",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo",
objectFile: "./testdata/diff-kustomization/key-sops-secret.yaml",
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-key-sops-secret.golden"),
},
{
name: "diff with a drifted value in sops secret object",
args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo",
objectFile: "./testdata/diff-kustomization/value-sops-secret.yaml",
assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-value-sops-secret.golden"),
},
}
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
b, _ := build.NewBuilder(kubeconfigArgs, "podinfo", "")
resourceManager, err := b.Manager()
if err != nil {
t.Fatal(err)
}
setup(t, tmpl)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.objectFile != "" {
resourceManager.ApplyAll(context.Background(), createObjectFromFile(tt.objectFile, tmpl, t), ssa.DefaultApplyOptions())
}
cmd := cmdTestCase{
args: tt.args + " -n " + tmpl["fluxns"],
assert: tt.assert,
}
cmd.runTestCmd(t)
if tt.objectFile != "" {
testEnv.DeleteObjectFile(tt.objectFile, tmpl, t)
}
})
}
}
func createObjectFromFile(objectFile string, templateValues map[string]string, t *testing.T) []*unstructured.Unstructured {
buf, err := os.ReadFile(objectFile)
if err != nil {
t.Fatalf("Error reading file '%s': %v", objectFile, err)
}
content, err := executeTemplate(string(buf), templateValues)
if err != nil {
t.Fatalf("Error evaluating template file '%s': '%v'", objectFile, err)
}
clientObjects, err := readYamlObjects(strings.NewReader(content))
if err != nil {
t.Fatalf("Error decoding yaml file '%s': %v", objectFile, err)
}
return clientObjects
}

View File

@@ -20,7 +20,6 @@ import (
"bytes"
"context"
"fmt"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -74,19 +73,19 @@ 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)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
if exportArgs.all {
err = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(*kubeconfigArgs.Namespace))
err = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(rootArgs.namespace))
if err != nil {
return err
}
if export.list.len() == 0 {
return fmt.Errorf("no objects found in %s namespace", *kubeconfigArgs.Namespace)
return fmt.Errorf("no objects found in %s namespace", rootArgs.namespace)
}
for i := 0; i < export.list.len(); i++ {
@@ -97,7 +96,7 @@ func (export exportCommand) run(cmd *cobra.Command, args []string) error {
} else {
name := args[0]
namespacedName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, export.object.asClientObject())

View File

@@ -19,7 +19,6 @@ package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -59,19 +58,19 @@ 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)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
if exportArgs.all {
err = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(*kubeconfigArgs.Namespace))
err = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(rootArgs.namespace))
if err != nil {
return err
}
if export.list.len() == 0 {
return fmt.Errorf("no objects found in %s namespace", *kubeconfigArgs.Namespace)
return fmt.Errorf("no objects found in %s namespace", rootArgs.namespace)
}
for i := 0; i < export.list.len(); i++ {
@@ -89,7 +88,7 @@ func (export exportWithSecretCommand) run(cmd *cobra.Command, args []string) err
} else {
name := args[0]
namespacedName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, export.object.asClientObject())

View File

@@ -1,4 +1,3 @@
//go:build unit
// +build unit
package main

View File

@@ -135,14 +135,14 @@ 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)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
var listOpts []client.ListOption
if !getArgs.allNamespaces {
listOpts = append(listOpts, client.InNamespace(*kubeconfigArgs.Namespace))
listOpts = append(listOpts, client.InNamespace(rootArgs.namespace))
}
if len(args) > 0 {
@@ -162,7 +162,7 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
if get.list.len() == 0 {
if !getAll {
logger.Failuref("no %s objects found in %s namespace", get.kind, *kubeconfigArgs.Namespace)
logger.Failuref("no %s objects found in %s namespace", get.kind, rootArgs.namespace)
}
return nil
}

View File

@@ -25,6 +25,9 @@ var getImageCmd = &cobra.Command{
Aliases: []string{"image"},
Short: "Get image automation object status",
Long: "The get image sub-commands print the status of image automation objects.",
RunE: func(cmd *cobra.Command, args []string) error {
return validateWatchOption(cmd, "images")
},
}
func init() {

View File

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

View File

@@ -25,6 +25,10 @@ var getSourceCmd = &cobra.Command{
Aliases: []string{"source"},
Short: "Get source statuses",
Long: "The get source sub-commands print the statuses of the sources.",
RunE: func(cmd *cobra.Command, args []string) error {
return validateWatchOption(cmd, "sources")
},
}
func init() {

View File

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

View File

@@ -1,4 +1,3 @@
//go:build e2e
// +build e2e
/*

View File

@@ -1,4 +1,3 @@
//go:build e2e
// +build e2e
package main

View File

@@ -131,7 +131,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Generatef("generating manifests")
}
tmpDir, err := os.MkdirTemp("", *kubeconfigArgs.Namespace)
tmpDir, err := os.MkdirTemp("", rootArgs.namespace)
if err != nil {
return err
}
@@ -148,7 +148,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
opts := install.Options{
BaseURL: installArgs.manifestsPath,
Version: installArgs.version,
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Components: components,
Registry: installArgs.registry,
ImagePullSecret: installArgs.imagePullSecret,
@@ -156,7 +156,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
NetworkPolicy: installArgs.networkPolicy,
LogLevel: installArgs.logLevel.String(),
NotificationController: rootArgs.defaults.NotificationController,
ManifestFile: fmt.Sprintf("%s.yaml", *kubeconfigArgs.Namespace),
ManifestFile: fmt.Sprintf("%s.yaml", rootArgs.namespace),
Timeout: rootArgs.timeout,
ClusterDomain: installArgs.clusterDomain,
TolerationKeys: installArgs.tolerationKeys,
@@ -183,21 +183,21 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
}
logger.Successf("manifests build completed")
logger.Actionf("installing components in %s namespace", *kubeconfigArgs.Namespace)
logger.Actionf("installing components in %s namespace", rootArgs.namespace)
if installArgs.dryRun {
logger.Successf("install dry-run finished")
return nil
}
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, filepath.Join(tmpDir, manifest.Path))
applyOutput, err := utils.Apply(ctx, rootArgs.kubeconfig, rootArgs.kubecontext, 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)
kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return fmt.Errorf("install failed: %w", err)
}

View File

@@ -1,4 +1,3 @@
//go:build e2e
// +build e2e
/*

View File

@@ -99,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)
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
@@ -278,7 +278,7 @@ 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) {
!logsArgs.allNamespaces && strings.ToLower(rootArgs.namespace) != strings.ToLower(l.Namespace) {
return
}

View File

@@ -1,4 +1,3 @@
//go:build unit
// +build unit
/*

View File

@@ -21,13 +21,13 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/spf13/cobra"
"golang.org/x/term"
corev1 "k8s.io/api/core/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
@@ -99,6 +99,9 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`,
var logger = stderrLogger{stderr: os.Stderr}
type rootFlags struct {
kubeconfig string
kubecontext string
namespace string
timeout time.Duration
verbose bool
pollInterval time.Duration
@@ -106,26 +109,19 @@ type rootFlags struct {
}
var rootArgs = NewRootFlags()
var kubeconfigArgs = genericclioptions.NewConfigFlags(false)
func init() {
rootCmd.PersistentFlags().StringVarP(&rootArgs.namespace, "namespace", "n", rootArgs.defaults.Namespace,
"the namespace scope for this operation, can be set with FLUX_SYSTEM_NAMESPACE env var")
rootCmd.RegisterFlagCompletionFunc("namespace", resourceNamesCompletionFunc(corev1.SchemeGroupVersion.WithKind("Namespace")))
rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 5*time.Minute, "timeout for this operation")
rootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, "verbose", false, "print generated objects")
rootCmd.PersistentFlags().StringVarP(&rootArgs.kubeconfig, "kubeconfig", "", "",
"absolute path to the kubeconfig file")
configureDefaultNamespace()
kubeconfigArgs.APIServer = nil // prevent AddFlags from configuring --server flag
kubeconfigArgs.Timeout = nil // prevent AddFlags from configuring --request-timeout flag, we have --timeout instead
kubeconfigArgs.AddFlags(rootCmd.PersistentFlags())
// Since some subcommands use the `-s` flag as a short version for `--silent`, we manually configure the server flag
// without the `-s` short version. While we're no longer on par with kubectl's flags, we maintain backwards compatibility
// on the CLI interface.
apiServer := ""
kubeconfigArgs.APIServer = &apiServer
rootCmd.PersistentFlags().StringVar(kubeconfigArgs.APIServer, "server", *kubeconfigArgs.APIServer, "The address and port of the Kubernetes API server")
rootCmd.PersistentFlags().StringVarP(&rootArgs.kubecontext, "context", "", "", "kubernetes context to use")
rootCmd.RegisterFlagCompletionFunc("context", contextsCompletionFunc)
rootCmd.RegisterFlagCompletionFunc("namespace", resourceNamesCompletionFunc(corev1.SchemeGroupVersion.WithKind("Namespace")))
rootCmd.DisableAutoGenTag = true
rootCmd.SetOut(os.Stdout)
@@ -142,17 +138,30 @@ func NewRootFlags() rootFlags {
func main() {
log.SetFlags(0)
configureKubeconfig()
configureDefaultNamespace()
if err := rootCmd.Execute(); err != nil {
logger.Failuref("%v", err)
os.Exit(1)
}
}
func configureKubeconfig() {
switch {
case len(rootArgs.kubeconfig) > 0:
case len(os.Getenv("KUBECONFIG")) > 0:
rootArgs.kubeconfig = os.Getenv("KUBECONFIG")
default:
if home := homeDir(); len(home) > 0 {
rootArgs.kubeconfig = filepath.Join(home, ".kube", "config")
}
}
}
func configureDefaultNamespace() {
*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace
fromEnv := os.Getenv("FLUX_SYSTEM_NAMESPACE")
if fromEnv != "" {
kubeconfigArgs.Namespace = &fromEnv
if fromEnv != "" && rootArgs.namespace == rootArgs.defaults.Namespace {
rootArgs.namespace = fromEnv
}
}

View File

@@ -1,4 +1,3 @@
//go:build e2e
// +build e2e
/*
@@ -36,7 +35,7 @@ func TestMain(m *testing.M) {
if err != nil {
panic(fmt.Errorf("error creating kube manager: '%w'", err))
}
kubeconfigArgs.KubeConfig = &testEnv.kubeConfigPath
rootArgs.kubeconfig = testEnv.kubeConfigPath
// Install Flux.
output, err := executeCommand("install --components-extra=image-reflector-controller,image-automation-controller")
@@ -55,7 +54,7 @@ func TestMain(m *testing.M) {
// Delete namespace and wait for finalisation
kubectlArgs := []string{"delete", "namespace", "flux-system"}
_, err = utils.ExecKubectlCommand(context.TODO(), utils.ModeStderrOS, *kubeconfigArgs.KubeConfig, *kubeconfigArgs.Context, kubectlArgs...)
_, err = utils.ExecKubectlCommand(context.TODO(), utils.ModeStderrOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
if err != nil {
panic(fmt.Errorf("delete namespace error:'%w'", err))
}
@@ -67,13 +66,13 @@ func TestMain(m *testing.M) {
func setupTestNamespace(namespace string) (func(), error) {
kubectlArgs := []string{"create", "namespace", namespace}
_, err := utils.ExecKubectlCommand(context.TODO(), utils.ModeStderrOS, *kubeconfigArgs.KubeConfig, *kubeconfigArgs.Context, kubectlArgs...)
_, err := utils.ExecKubectlCommand(context.TODO(), utils.ModeStderrOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
if err != nil {
return nil, err
}
return func() {
kubectlArgs := []string{"delete", "namespace", namespace}
utils.ExecKubectlCommand(context.TODO(), utils.ModeCapture, *kubeconfigArgs.KubeConfig, *kubeconfigArgs.Context, kubectlArgs...)
utils.ExecKubectlCommand(context.TODO(), utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
}, nil
}

View File

@@ -49,8 +49,8 @@ func allocateNamespace(prefix string) string {
return fmt.Sprintf("%s-%d", prefix, id)
}
func readYamlObjects(rdr io.Reader) ([]*unstructured.Unstructured, error) {
objects := []*unstructured.Unstructured{}
func readYamlObjects(rdr io.Reader) ([]unstructured.Unstructured, error) {
objects := []unstructured.Unstructured{}
reader := k8syaml.NewYAMLReader(bufio.NewReader(rdr))
for {
doc, err := reader.Read()
@@ -65,7 +65,7 @@ func readYamlObjects(rdr io.Reader) ([]*unstructured.Unstructured, error) {
if err != nil {
return nil, err
}
objects = append(objects, unstructuredObj)
objects = append(objects, *unstructuredObj)
}
return objects, nil
}
@@ -96,7 +96,7 @@ func (m *testEnvKubeManager) CreateObjectFile(objectFile string, templateValues
}
}
func (m *testEnvKubeManager) CreateObjects(clientObjects []*unstructured.Unstructured, t *testing.T) error {
func (m *testEnvKubeManager) CreateObjects(clientObjects []unstructured.Unstructured, t *testing.T) error {
for _, obj := range clientObjects {
// First create the object then set its status if present in the
// yaml file. Make a copy first since creating an object may overwrite
@@ -107,7 +107,7 @@ func (m *testEnvKubeManager) CreateObjects(clientObjects []*unstructured.Unstruc
return err
}
obj.SetResourceVersion(createObj.GetResourceVersion())
err = m.client.Status().Update(context.Background(), obj)
err = m.client.Status().Update(context.Background(), &obj)
if err != nil {
return err
}
@@ -115,36 +115,6 @@ func (m *testEnvKubeManager) CreateObjects(clientObjects []*unstructured.Unstruc
return nil
}
func (m *testEnvKubeManager) DeleteObjectFile(objectFile string, templateValues map[string]string, t *testing.T) {
buf, err := os.ReadFile(objectFile)
if err != nil {
t.Fatalf("Error reading file '%s': %v", objectFile, err)
}
content, err := executeTemplate(string(buf), templateValues)
if err != nil {
t.Fatalf("Error evaluating template file '%s': '%v'", objectFile, err)
}
clientObjects, err := readYamlObjects(strings.NewReader(content))
if err != nil {
t.Fatalf("Error decoding yaml file '%s': %v", objectFile, err)
}
err = m.DeleteObjects(clientObjects, t)
if err != nil {
t.Logf("Error deleting test objects: '%v'", err)
}
}
func (m *testEnvKubeManager) DeleteObjects(clientObjects []*unstructured.Unstructured, t *testing.T) error {
for _, obj := range clientObjects {
err := m.client.Delete(context.Background(), obj)
if err != nil {
return err
}
}
return nil
}
func (m *testEnvKubeManager) Stop() error {
if m.testEnv == nil {
return fmt.Errorf("do nothing because testEnv is nil")

View File

@@ -1,4 +1,3 @@
//go:build unit
// +build unit
/*
@@ -43,8 +42,7 @@ func TestMain(m *testing.M) {
panic(fmt.Errorf("error creating kube manager: '%w'", err))
}
testEnv = km
// rootArgs.kubeconfig = testEnv.kubeConfigPath
kubeconfigArgs.KubeConfig = &testEnv.kubeConfigPath
rootArgs.kubeconfig = testEnv.kubeConfigPath
// Run tests
code := m.Run()

View File

@@ -75,13 +75,13 @@ 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)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Name: name,
}
@@ -94,7 +94,7 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
return fmt.Errorf("resource is suspended")
}
logger.Actionf("annotating %s %s in %s namespace", reconcile.kind, name, *kubeconfigArgs.Namespace)
logger.Actionf("annotating %s %s in %s namespace", reconcile.kind, name, rootArgs.namespace)
if err := requestReconciliation(ctx, kubeClient, namespacedName, reconcile.object); err != nil {
return err
}

View File

@@ -54,17 +54,17 @@ func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Name: name,
}
logger.Actionf("annotating Provider %s in %s namespace", name, *kubeconfigArgs.Namespace)
logger.Actionf("annotating Provider %s in %s namespace", name, rootArgs.namespace)
var alertProvider notificationv1.Provider
err = kubeClient.Get(ctx, namespacedName, &alertProvider)
if err != nil {

View File

@@ -54,13 +54,13 @@ func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Name: name,
}
@@ -74,7 +74,7 @@ func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("resource is suspended")
}
logger.Actionf("annotating Receiver %s in %s namespace", name, *kubeconfigArgs.Namespace)
logger.Actionf("annotating Receiver %s in %s namespace", name, rootArgs.namespace)
if receiver.Annotations == nil {
receiver.Annotations = map[string]string{
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),

View File

@@ -36,13 +36,13 @@ func (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []strin
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Name: name,
}
@@ -57,20 +57,20 @@ func (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []strin
if reconcile.object.reconcileSource() {
reconcileCmd, nsName := reconcile.object.getSource()
nsCopy := *kubeconfigArgs.Namespace
nsCopy := rootArgs.namespace
if nsName.Namespace != "" {
*kubeconfigArgs.Namespace = nsName.Namespace
rootArgs.namespace = nsName.Namespace
}
err := reconcileCmd.run(nil, []string{nsName.Name})
if err != nil {
return err
}
*kubeconfigArgs.Namespace = nsCopy
rootArgs.namespace = nsCopy
}
lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()
logger.Actionf("annotating %s %s in %s namespace", reconcile.kind, name, *kubeconfigArgs.Namespace)
logger.Actionf("annotating %s %s in %s namespace", reconcile.kind, name, rootArgs.namespace)
if err := requestReconciliation(ctx, kubeClient, namespacedName, reconcile.object); err != nil {
return err
}

View File

@@ -72,13 +72,13 @@ func (resume resumeCommand) run(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
var listOpts []client.ListOption
listOpts = append(listOpts, client.InNamespace(*kubeconfigArgs.Namespace))
listOpts = append(listOpts, client.InNamespace(rootArgs.namespace))
if len(args) > 0 {
listOpts = append(listOpts, client.MatchingFields{
"metadata.name": args[0],
@@ -91,12 +91,12 @@ func (resume resumeCommand) run(cmd *cobra.Command, args []string) error {
}
if resume.list.len() == 0 {
logger.Failuref("no %s objects found in %s namespace", resume.kind, *kubeconfigArgs.Namespace)
logger.Failuref("no %s objects found in %s namespace", resume.kind, rootArgs.namespace)
return nil
}
for i := 0; i < resume.list.len(); i++ {
logger.Actionf("resuming %s %s in %s namespace", resume.humanKind, resume.list.resumeItem(i).asClientObject().GetName(), *kubeconfigArgs.Namespace)
logger.Actionf("resuming %s %s in %s namespace", resume.humanKind, resume.list.resumeItem(i).asClientObject().GetName(), rootArgs.namespace)
resume.list.resumeItem(i).setUnsuspended()
if err := kubeClient.Update(ctx, resume.list.resumeItem(i).asClientObject()); err != nil {
return err
@@ -105,7 +105,7 @@ func (resume resumeCommand) run(cmd *cobra.Command, args []string) error {
namespacedName := types.NamespacedName{
Name: resume.list.resumeItem(i).asClientObject().GetName(),
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
}
logger.Waitingf("waiting for %s reconciliation", resume.kind)

View File

@@ -69,11 +69,11 @@ func isReady(ctx context.Context, kubeClient client.Client,
func buildComponentObjectRefs(components ...string) ([]object.ObjMetadata, error) {
var objRefs []object.ObjMetadata
for _, deployment := range components {
objRefs = append(objRefs, object.ObjMetadata{
Namespace: *kubeconfigArgs.Namespace,
Name: deployment,
GroupKind: schema.GroupKind{Group: "apps", Kind: "Deployment"},
})
objMeta, err := object.CreateObjMetadata(rootArgs.namespace, deployment, schema.GroupKind{Group: "apps", Kind: "Deployment"})
if err != nil {
return nil, err
}
objRefs = append(objRefs, objMeta)
}
return objRefs, nil
}

View File

@@ -69,13 +69,13 @@ func (suspend suspendCommand) run(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
var listOpts []client.ListOption
listOpts = append(listOpts, client.InNamespace(*kubeconfigArgs.Namespace))
listOpts = append(listOpts, client.InNamespace(rootArgs.namespace))
if len(args) > 0 {
listOpts = append(listOpts, client.MatchingFields{
"metadata.name": args[0],
@@ -88,12 +88,12 @@ func (suspend suspendCommand) run(cmd *cobra.Command, args []string) error {
}
if suspend.list.len() == 0 {
logger.Failuref("no %s objects found in %s namespace", suspend.kind, *kubeconfigArgs.Namespace)
logger.Failuref("no %s objects found in %s namespace", suspend.kind, rootArgs.namespace)
return nil
}
for i := 0; i < suspend.list.len(); i++ {
logger.Actionf("suspending %s %s in %s namespace", suspend.humanKind, suspend.list.item(i).asClientObject().GetName(), *kubeconfigArgs.Namespace)
logger.Actionf("suspending %s %s in %s namespace", suspend.humanKind, suspend.list.item(i).asClientObject().GetName(), rootArgs.namespace)
suspend.list.item(i).setSuspended()
if err := kubeClient.Update(ctx, suspend.list.item(i).asClientObject()); err != nil {
return err

View File

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

View File

@@ -1,20 +0,0 @@
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: podinfo
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
minReplicas: 2
maxReplicas: 4
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
# scale up if usage is above
# 99% of the requested CPU (100m)
averageUtilization: 99

View File

@@ -1,5 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./deployment.yaml
- ./hpa.yaml

View File

@@ -1,15 +0,0 @@
---
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

View File

@@ -1,148 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: podinfo
namespace: default
spec:
minReadySeconds: 3
progressDeadlineSeconds: 60
revisionHistoryLimit: 5
selector:
matchLabels:
app: podinfo
strategy:
rollingUpdate:
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
annotations:
prometheus.io/port: "9797"
prometheus.io/scrape: "true"
labels:
app: podinfo
spec:
containers:
- command:
- ./podinfo
- --port=9898
- --port-metrics=9797
- --grpc-port=9999
- --grpc-service-name=podinfo
- --level=info
- --random-delay=false
- --random-error=false
env:
- name: PODINFO_UI_COLOR
value: '#34577c'
image: ghcr.io/stefanprodan/podinfo:6.0.10
imagePullPolicy: IfNotPresent
livenessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/healthz
initialDelaySeconds: 5
timeoutSeconds: 5
name: podinfod
ports:
- containerPort: 9898
name: http
protocol: TCP
- containerPort: 9797
name: http-metrics
protocol: TCP
- containerPort: 9999
name: grpc
protocol: TCP
readinessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/readyz
initialDelaySeconds: 5
timeoutSeconds: 5
resources:
limits:
cpu: 2000m
memory: 512Mi
requests:
cpu: 100m
memory: 64Mi
---
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: podinfo
namespace: default
spec:
maxReplicas: 4
metrics:
- resource:
name: cpu
target:
averageUtilization: 99
type: Utilization
type: Resource
minReplicas: 2
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
---
apiVersion: v1
kind: Service
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: podinfo
namespace: default
spec:
ports:
- name: http
port: 9898
protocol: TCP
targetPort: http
- name: grpc
port: 9999
protocol: TCP
targetPort: grpc
selector:
app: podinfo
type: ClusterIP
---
apiVersion: v1
data:
token: KipTT1BTKio=
kind: Secret
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: podinfo-token-77t89m9b67
namespace: default
type: Opaque
---
apiVersion: v1
data:
password: MWYyZDFlMmU2N2Rm
username: YWRtaW4=
kind: Secret
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: db-user-pass-bkbd782d2c
namespace: default
type: Opaque

View File

@@ -1,16 +0,0 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: {{ .fluxns }}
---
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
name: podinfo
namespace: {{ .fluxns }}
spec:
interval: 30s
ref:
branch: master
url: https://github.com/stefanprodan/podinfo

View File

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

View File

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

View File

@@ -1,20 +0,0 @@
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: podinfo
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
minReplicas: 2
maxReplicas: 4
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
# scale up if usage is above
# 99% of the requested CPU (100m)
averageUtilization: 99

View File

@@ -1,14 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./deployment.yaml
- ./hpa.yaml
- ./service.yaml
secretGenerator:
- files:
- token=token.encrypted
name: podinfo-token
- literals:
- username=admin
- password=1f2d1e2e67df
name: db-user-pass

View File

@@ -1,17 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: podinfo
spec:
type: ClusterIP
selector:
app: podinfo
ports:
- name: http
port: 9898
protocol: TCP
targetPort: http
- port: 9999
targetPort: grpc
protocol: TCP
name: grpc

View File

@@ -1,20 +0,0 @@
{
"data": "ENC[AES256_GCM,data:oBe5PlPmfQCUUc4sqKImjw==,iv:MLLEW15QC9kRdVVagJnzLCSk0xZGWIpAeTfHzyxT10g=,tag:K3GkBCGS+ut4Tpk6ndb0CA==,type:str]",
"sops": {
"kms": null,
"gcp_kms": null,
"azure_kv": null,
"hc_vault": null,
"age": [
{
"recipient": "age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+ IFgyNTUxOSA1L2RpZHRrK1FSVmYrd1Va\nY0hxWFQzSDBsT1k3WjNtYmU1QmliaDJycXlNCnF1YjdNOThVbVNvMG9rNS9ZUXZw\nMnV0bnRUMGNtejFPbzM4U2UzWkszeVkKLS0tIGJ6UGhxMUV3YmVJTHlJSUJpRVRZ\nVjd0RVRadU8wekxXTHIrYUplYkN2aEEK0I/ MCEtXRk+b/N2G1JF3vHQT24dShWYD\nw+JIUSA3aLf2sv0zr2MdUEdVWBJoM8nT4D4xVbBORD+669W+9nDeSw==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2021-11-26T16:34:51Z",
"mac": "ENC[AES256_GCM,data:COGzf5YCHNNP6z4JaEKrjN3M8f5+Q1uKUKTMHwj388/ICmLyi2sSrTmj7PP+X7M9jTVwa8wVgYTpNLiVJx+LcxqvIXM0Tyo+/Cu1zrfao98aiACP8+TSEDiFQNtEus23H+d/X1hqMwRHDI3kQ+ 6scgEGnqY57r3RDSA3E8EhHr4=,iv:LxitVIYm8srZVqFueJh9loClA44Y2Z3XAVYmxesMmOg=,tag:Y8qFD8UGlDfwNSv7xlcn6A==,type:str]",
"pgp": null,
"unencrypted_suffix": "_unencrypted",
"version": "3.7.1"
}
}

View File

@@ -1,3 +1,3 @@
► checking prerequisites
✔ Kubernetes {{ .serverVersion }} >=1.20.6-0
✔ Kubernetes {{ .serverVersion }} >=1.19.0-0
✔ prerequisites checks passed

View File

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

View File

@@ -1,4 +0,0 @@
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo created
► Secret/default/podinfo-token-77t89m9b67 created
► Secret/default/db-user-pass-bkbd782d2c created

View File

@@ -1,10 +0,0 @@
► Deployment/default/podinfo created
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo created
► Secret/default/podinfo-token-77t89m9b67 drifted
data
- one map entry removed: + one map entry added:
drift-key: "*****" token: "*****"
► Secret/default/db-user-pass-bkbd782d2c created

View File

@@ -1,11 +0,0 @@
► Deployment/default/podinfo created
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo created
► Secret/default/podinfo-token-77t89m9b67 created
► Secret/default/db-user-pass-bkbd782d2c drifted
data.password
± value change
- ******
+ *****

View File

@@ -1,11 +0,0 @@
► Deployment/default/podinfo created
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo drifted
spec.ports.http.port
± value change
- 9899
+ 9898
► Secret/default/podinfo-token-77t89m9b67 created
► Secret/default/db-user-pass-bkbd782d2c created

View File

@@ -1,4 +0,0 @@
► Deployment/default/podinfo created
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo created
► Secret/default/db-user-pass-bkbd782d2c created

View File

@@ -1,11 +0,0 @@
apiVersion: v1
data:
drift-key: bXktc2VjcmV0LXRva2VuCg==
kind: Secret
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: podinfo-token-77t89m9b67
namespace: default
type: Opaque

View File

@@ -1,11 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./deployment.yaml
- ./hpa.yaml
- ./service.yaml
secretGenerator:
- literals:
- username=admin
- password=1f2d1e2e67df
name: secret-basic-auth

View File

@@ -1,5 +0,0 @@
► Deployment/default/podinfo created
► HorizontalPodAutoscaler/default/podinfo created
► Service/default/podinfo created
► Secret/default/podinfo-token-77t89m9b67 created
► Secret/default/db-user-pass-bkbd782d2c created

View File

@@ -1,12 +0,0 @@
apiVersion: v1
data:
password: cGFzc3dvcmQK
username: YWRtaW4=
kind: Secret
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: db-user-pass-bkbd782d2c
namespace: default
type: Opaque

View File

@@ -1,21 +0,0 @@
apiVersion: v1
kind: Service
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: podinfo
namespace: default
spec:
type: ClusterIP
selector:
app: podinfo
ports:
- name: http
port: 9899
protocol: TCP
targetPort: http
- port: 9999
targetPort: grpc
protocol: TCP
name: grpc

View File

@@ -1,11 +0,0 @@
apiVersion: v1
data:
token: ZHJpZnQtdmFsdWUK
kind: Secret
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: podinfo
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
name: podinfo-token-77t89m9b67
namespace: default
type: Opaque

View File

@@ -1,2 +1,2 @@
NAME READY MESSAGE REVISION SUSPENDED
tkfg True Applied revision: 6.0.0/627d5c4 6.0.0/627d5c4 False
NAME READY MESSAGE REVISION SUSPENDED
tkfg True Applied revision: 6.0.0/627d5c4bb67b77185f37e31d734b085019ff2951 6.0.0/627d5c4bb67b77185f37e31d734b085019ff2951 False

View File

@@ -27,10 +27,8 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/resource"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils"
@@ -41,26 +39,20 @@ import (
)
var traceCmd = &cobra.Command{
Use: "trace <resource> <name> [<name> ...]",
Short: "Trace in-cluster objects throughout the GitOps delivery pipeline",
Long: `The trace command shows how one or more objects are managed by Flux,
from which source and revision they come, and what the latest reconciliation status is.
You can also trace multiple objects with different resource kinds using <resource>/<name> multiple times.`,
Use: "trace [name]",
Short: "Trace an in-cluster object throughout the GitOps delivery pipeline",
Long: `The trace command shows how an object is managed by Flux,
from which source and revision it comes, and what's the latest reconciliation status.'`,
Example: ` # Trace a Kubernetes Deployment
flux trace -n apps deployment my-app
flux trace my-app --kind=deployment --api-version=apps/v1 --namespace=apps
# Trace a Kubernetes Pod and a config map
flux trace -n redis pod/redis-master-0 cm/redis
# Trace a Kubernetes Pod
flux trace redis-master-0 --kind=pod --api-version=v1 -n redis
# Trace a Kubernetes global object
flux trace namespace redis
flux trace redis --kind=namespace --api-version=v1
# Trace a Kubernetes custom resource
flux trace -n redis helmrelease redis
# API Version and Kind can also be specified explicitly
# Note that either both, kind and api-version, or neither have to be specified.
flux trace redis --kind=helmrelease --api-version=helm.toolkit.fluxcd.io/v2beta1 -n redis`,
RunE: traceCmdRun,
}
@@ -81,43 +73,49 @@ func init() {
}
func traceCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("object name is required")
}
name := args[0]
if traceArgs.kind == "" {
return fmt.Errorf("object kind is required (--kind)")
}
if traceArgs.apiVersion == "" {
return fmt.Errorf("object apiVersion is required (--api-version)")
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
var objects []*unstructured.Unstructured
if traceArgs.kind != "" || traceArgs.apiVersion != "" {
var obj *unstructured.Unstructured
obj, err = getObjectStatic(ctx, kubeClient, args)
objects = []*unstructured.Unstructured{obj}
} else {
objects, err = getObjectDynamic(args)
}
gv, err := schema.ParseGroupVersion(traceArgs.apiVersion)
if err != nil {
return err
return fmt.Errorf("invaild apiVersion: %w", err)
}
return traceObjects(ctx, kubeClient, objects)
}
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(schema.GroupVersionKind{
Group: gv.Group,
Version: gv.Version,
Kind: traceArgs.kind,
})
func traceObjects(ctx context.Context, kubeClient client.Client, objects []*unstructured.Unstructured) error {
for i, obj := range objects {
err := traceObject(ctx, kubeClient, obj)
if err != nil {
rootCmd.PrintErrf("failed to trace %v/%v in namespace %v: %v", obj.GetKind(), obj.GetName(), obj.GetNamespace(), err)
}
if i < len(objects)-1 {
rootCmd.Println("---")
}
objName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
err = kubeClient.Get(ctx, objName, obj)
if err != nil {
return fmt.Errorf("failed to find object: %w", err)
}
return nil
}
func traceObject(ctx context.Context, kubeClient client.Client, obj *unstructured.Unstructured) error {
if ks, ok := isOwnerManagedByFlux(ctx, kubeClient, obj, kustomizev1.GroupVersion.Group); ok {
report, err := traceKustomization(ctx, kubeClient, ks, obj)
if err != nil {
@@ -139,85 +137,14 @@ func traceObject(ctx context.Context, kubeClient client.Client, obj *unstructure
return fmt.Errorf("object not managed by Flux")
}
func getObjectStatic(ctx context.Context, kubeClient client.Client, args []string) (*unstructured.Unstructured, error) {
if len(args) < 1 {
return nil, fmt.Errorf("object name is required")
}
if traceArgs.kind == "" {
return nil, fmt.Errorf("object kind is required (--kind)")
}
if traceArgs.apiVersion == "" {
return nil, fmt.Errorf("object apiVersion is required (--api-version)")
}
gv, err := schema.ParseGroupVersion(traceArgs.apiVersion)
if err != nil {
return nil, fmt.Errorf("invaild apiVersion: %w", err)
}
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(schema.GroupVersionKind{
Group: gv.Group,
Version: gv.Version,
Kind: traceArgs.kind,
})
objName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Name: args[0],
}
if err = kubeClient.Get(ctx, objName, obj); err != nil {
return nil, fmt.Errorf("failed to find object: %w", err)
}
return obj, nil
}
func getObjectDynamic(args []string) ([]*unstructured.Unstructured, error) {
r := resource.NewBuilder(kubeconfigArgs).
Unstructured().
NamespaceParam(*kubeconfigArgs.Namespace).DefaultNamespace().
ResourceTypeOrNameArgs(false, args...).
ContinueOnError().
Latest().
Do()
if err := r.Err(); err != nil {
if resource.IsUsageError(err) {
return nil, fmt.Errorf("either `<resource>/<name>` or `<resource> <name>` is required as an argument")
}
return nil, err
}
infos, err := r.Infos()
if err != nil {
return nil, fmt.Errorf("x: %v", err)
}
if len(infos) == 0 {
return nil, fmt.Errorf("failed to find object: %w", err)
}
objects := []*unstructured.Unstructured{}
for _, info := range infos {
obj := &unstructured.Unstructured{}
obj.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(info.Object)
if err != nil {
return objects, err
}
objects = append(objects, obj)
}
return objects, nil
}
func traceKustomization(ctx context.Context, kubeClient client.Client, ksName types.NamespacedName, obj *unstructured.Unstructured) (string, error) {
ks := &kustomizev1.Kustomization{}
ksReady := &metav1.Condition{}
err := kubeClient.Get(ctx, ksName, ks)
if err != nil {
return "", fmt.Errorf("failed to find kustomization: %w", err)
}
ksReady := meta.FindStatusCondition(ks.Status.Conditions, fluxmeta.ReadyCondition)
ksReady = meta.FindStatusCondition(ks.Status.Conditions, fluxmeta.ReadyCondition)
var ksRepository *sourcev1.GitRepository
var ksRepositoryReady *metav1.Condition
@@ -325,11 +252,12 @@ Status: Unknown
func traceHelm(ctx context.Context, kubeClient client.Client, hrName types.NamespacedName, obj *unstructured.Unstructured) (string, error) {
hr := &helmv2.HelmRelease{}
hrReady := &metav1.Condition{}
err := kubeClient.Get(ctx, hrName, hr)
if err != nil {
return "", fmt.Errorf("failed to find HelmRelease: %w", err)
}
hrReady := meta.FindStatusCondition(hr.Status.Conditions, fluxmeta.ReadyCondition)
hrReady = meta.FindStatusCondition(hr.Status.Conditions, fluxmeta.ReadyCondition)
var hrChart *sourcev1.HelmChart
var hrChartReady *metav1.Condition

View File

@@ -1,4 +1,3 @@
//go:build unit
// +build unit
package main
@@ -10,7 +9,7 @@ import (
func TestTraceNoArgs(t *testing.T) {
cmd := cmdTestCase{
args: "trace",
assert: assertError("either `<resource>/<name>` or `<resource> <name>` is required as an argument"),
assert: assertError("object name is required"),
}
cmd.runTestCmd(t)
}

View File

@@ -77,26 +77,27 @@ func treeKsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
k := &kustomizev1.Kustomization{}
err = kubeClient.Get(ctx, client.ObjectKey{
Namespace: *kubeconfigArgs.Namespace,
Namespace: rootArgs.namespace,
Name: name,
}, k)
if err != nil {
return err
}
kTree := tree.New(object.ObjMetadata{
Namespace: k.Namespace,
Name: k.Name,
GroupKind: schema.GroupKind{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind},
})
kMeta, err := object.CreateObjMetadata(k.Namespace, k.Name,
schema.GroupKind{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind})
if err != nil {
return err
}
kTree := tree.New(kMeta)
err = treeKustomization(ctx, kTree, k, kubeClient, treeKsArgs.compact)
if err != nil {
return err
@@ -272,5 +273,5 @@ func getHelmReleaseInventory(ctx context.Context, objectKey client.ObjectKey, ku
return nil, fmt.Errorf("failed to read the Helm storage object for HelmRelease '%s': %w", objectKey.String(), err)
}
return object.UnstructuredSetToObjMetadataSet(objects), nil
return object.UnstructuredsToObjMetas(objects)
}

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