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

Compare commits

..

49 Commits

Author SHA1 Message Date
Stefan Prodan
bce0da2806 Merge pull request #1226 from fluxcd/bootstrap-git-docs
Revamp bootstrap documentation
2021-04-08 15:23:42 +03:00
Stefan Prodan
a58c40f2d7 Add note about providing a SSH key to bootstrap
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-08 14:06:42 +03:00
Stefan Prodan
65d5cadf29 Update the alert providers list in notifications guide
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-08 13:31:21 +03:00
Stefan Prodan
1ea5d4d2e3 Remove sourceignore from SOPS guide
No longer needed due to https://github.com/fluxcd/source-controller/pull/329

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-08 13:31:16 +03:00
Stefan Prodan
719ef3c44c Add flux CLI container image to docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-08 13:31:16 +03:00
Stefan Prodan
f4adfc3029 Add bootstrap git to install docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-08 13:31:16 +03:00
Stefan Prodan
d8d08091cc Move Azure DevOps bootstrap to Azure docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-08 13:31:15 +03:00
Stefan Prodan
de4b3ef3dc Merge pull request #1231 from fluxcd/git-custom-pk
Take private key from file into account in Git bootstrap
2021-04-08 13:30:51 +03:00
Hidde Beydals
7bd6aedb73 Take PK from file into account in Git bootstrap
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-08 12:06:33 +02:00
Stefan Prodan
fffe40fbd4 Merge pull request #1222 from fluxcd/fix-git-http
Fix create source git auth for non-HTTPS repos
2021-04-08 11:11:03 +03:00
Stefan Prodan
74feda73af Add Warningf to logger interface amd impl
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-08 10:48:27 +03:00
Stefan Prodan
8b5583930e Fix create source git auth for non-HTTPS repos
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-08 10:29:23 +03:00
Hidde Beydals
466fdae70e Merge pull request #1225 from SomtochiAma/refactor-last-cmd
Format go imports in cmd/flux
2021-04-07 18:05:30 +02:00
Somtochi Onyekwere
054a62fb30 Format go imports
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-04-07 16:40:16 +01:00
Hidde Beydals
c694b570e0 Merge pull request #1224 from SomtochiAma/refactor-last-cmd
Refactor reconcile and resume cmd for alert and receiver
2021-04-07 15:02:22 +02:00
Somtochi Onyekwere
4204ec1d43 Refactor reconcile and resume cmd for alert and receiver
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-04-07 13:35:12 +01:00
Stefan Prodan
6d4e37ccb2 Merge pull request #1223 from fluxcd/cii-badge
Add CII Best Practices badge
2021-04-07 14:39:05 +03:00
Stefan Prodan
eef06c993e Add CII Best Practices badge
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-07 14:13:23 +03:00
Stefan Prodan
58362fbbb9 Merge pull request #1191 from fluxcd/recurse-submodules
Add recurse submodules arg to create source git and bootstrap cmd
2021-04-07 13:12:05 +03:00
Stefan Prodan
b872e595ae Add recurse submodules arg to bootstrap cmd
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-07 12:56:51 +03:00
Stefan Prodan
18c3f79319 Add recurse submodules arg to create source git cmd
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-07 12:01:25 +03:00
Stefan Prodan
8f0cd35d7a Allow self-signed certs when using go-git
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-04-07 12:00:48 +03:00
Stefan Prodan
c8bcf19f32 Merge pull request #1194 from fluxcd/update-components
Update toolkit components
2021-04-07 11:53:33 +03:00
fluxcdbot
5bee3047ac Update toolkit components
- kustomize-controller to v0.11.0
  https://github.com/fluxcd/kustomize-controller/blob/v0.11.0/CHANGELOG.md
- notification-controller to v0.12.0
  https://github.com/fluxcd/notification-controller/blob/v0.12.0/CHANGELOG.md
- image-reflector-controller to v0.8.0
  https://github.com/fluxcd/image-reflector-controller/blob/v0.8.0/CHANGELOG.md
- image-automation-controller to v0.8.0
  https://github.com/fluxcd/image-automation-controller/blob/v0.8.0/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2021-04-07 08:41:09 +00:00
Hidde Beydals
0d2f6bf02d Merge pull request #968 from fluxcd/go-git-providers-bootstrap 2021-04-07 10:40:30 +02:00
Hidde Beydals
7481c6beb0 Retry reconcile and clone actions once
We have observed that the code at times outperforms GitHub mechanics,
resulting in not found errors that are only true for a millisecond.
Retrying those actions once with a 2 second delay should be more
friendly to users.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-07 10:24:08 +02:00
Hidde Beydals
4ece12348b Ignore broken symlinks and outside path, in commit
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-07 10:24:08 +02:00
Hidde Beydals
e65a5beaae Work around custom client domain issue
With this commit comes a lot of evil.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-07 10:24:08 +02:00
Hidde Beydals
ef576128e3 Use correct hostname argument for secret gen
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-07 10:24:08 +02:00
Hidde Beydals
7f0bc2ada2 Provide option to add appendix to commit messages
Using the `--commit-message-appendix` flag a string can be added to the
commit messages made by the bootstrapper process to for example skip CI
actions from executing using e.g. `[skip ci]`.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-07 10:24:08 +02:00
Hidde Beydals
96c373d045 Properly configure sync URL based on auth settings
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-07 10:24:08 +02:00
Hidde Beydals
22648cae3b Add command to bootstrap to generic Git server
This command makes it possible to bootstrap to a generic Git server
using the local SSH agent, or a given password or private key file.

If a private key is generated, the user is prompted to give the
generated key access to the repository.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-07 10:24:08 +02:00
Hidde Beydals
f57ce14754 Implement bootstrap package in commands
This includes making a lot of things configurable (e.g. SSH key
algorithm, RSA bit size, etc.) that used to be static.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-07 10:24:08 +02:00
Hidde Beydals
1d3a381389 Test giving access to team in bootstrap e2e
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-07 10:24:08 +02:00
Hidde Beydals
9055e753a9 Add app.kubernetes.io/part-of: flux label
To be used in a future version of Flux to better select Flux components
in a namespace, as the namespace value for the
`app.kubernetes.io/instance` could be used by non Flux related
workloads.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-07 10:24:08 +02:00
Hidde Beydals
6390812cbb Factor bootstrap logic into bootstrap package
This commit factors out the bootstrap logic into a new `bootstrap`
package, while also moving to `go-git-providers` to handle things
around Git providers (e.g. repository creation, deploy key
upsertions).

The `GitProviderBootstrapper` is a superset of the
`PlainGitBootstrapper` that besides `Reconciler` also implements the
`RepositoryReconciler`.

The Git actions rely on an interface, making it easier to support
other implementations than `go-git` at a later moment, to for example
support bootstrapping to Git servers that only support the v2 protocol.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-04-07 10:24:08 +02:00
Hidde Beydals
fa46f05423 Merge pull request #1219 from frankgu968/main
fix: install command flags logic bug
2021-04-06 17:13:06 +02:00
Frank Gu
6b0ffe0b13 fix: install command flags logic bug
Signed-off-by: Frank Gu <frank.gu968@outlook.com>
2021-04-06 07:57:36 -07:00
Stefan Prodan
e724d90202 Merge pull request #1190 from fluxcd/update-components
Update source-controller to v0.11.0
2021-04-01 09:22:42 +03:00
fluxcdbot
6129943685 Update toolkit components
- source-controller to v0.11.0
  https://github.com/fluxcd/source-controller/blob/v0.11.0/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2021-03-31 17:38:37 +00:00
Hidde Beydals
d4e37cbda5 Merge pull request #1179 from hiddeco/docker-images
Publish AMD64, ARM64, ARMv7 images for binary
2021-03-30 12:02:06 +02:00
Hidde Beydals
cccfb3a560 Merge pull request #1176 from kaaboaye/patch-2
Fix reocncile typo
2021-03-30 11:23:17 +02:00
Hidde Beydals
d0403038ed Enable QEMU and Docker Buildx in release action
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-03-30 11:22:31 +02:00
Hidde Beydals
a5a7d7970f Publish AMD64, ARM64, ARMv7 images for binary
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-03-30 11:22:31 +02:00
Mieszko Wawrzyniak
62b9377f15 Fix reocncile typo
Signed-off-by: kaaboaye <kaaboaye@gmail.com>
2021-03-30 10:20:37 +02:00
Stefan Prodan
ec2c71f9ef Merge pull request #1173 from fluxcd/source-namespace
Add source namespace to create commands
2021-03-29 13:22:08 +03:00
Stefan Prodan
b54fd2c6b3 Add source namespace to create commands
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-03-29 11:42:04 +03:00
Stefan Prodan
e5066c3712 Merge pull request #1171 from Legion2/patch-1
Updated automation migration guide filter tags crd
2021-03-29 10:25:22 +03:00
Leon Kiefer
fd1c038303 Updated automation migration guide filter tags crd
Signed-off-by: Leon Kiefer <leon.k97@gmx.de>
2021-03-28 21:09:10 +02:00
108 changed files with 3133 additions and 1489 deletions

View File

@@ -47,7 +47,8 @@ jobs:
--owner=fluxcd-testing \ --owner=fluxcd-testing \
--repository=${{ steps.vars.outputs.test_repo_name }} \ --repository=${{ steps.vars.outputs.test_repo_name }} \
--branch=main \ --branch=main \
--path=test-cluster --path=test-cluster \
--team=team-z
env: env:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: bootstrap no-op - name: bootstrap no-op
@@ -56,7 +57,8 @@ jobs:
--owner=fluxcd-testing \ --owner=fluxcd-testing \
--repository=${{ steps.vars.outputs.test_repo_name }} \ --repository=${{ steps.vars.outputs.test_repo_name }} \
--branch=main \ --branch=main \
--path=test-cluster --path=test-cluster \
--team=team-z
env: env:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: uninstall - name: uninstall
@@ -69,7 +71,8 @@ jobs:
--owner=fluxcd-testing \ --owner=fluxcd-testing \
--repository=${{ steps.vars.outputs.test_repo_name }} \ --repository=${{ steps.vars.outputs.test_repo_name }} \
--branch=main \ --branch=main \
--path=test-cluster --path=test-cluster \
--team=team-z
env: env:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: delete repository - name: delete repository

View File

@@ -121,7 +121,7 @@ jobs:
run: | run: |
/tmp/flux create hr podinfo-helm \ /tmp/flux create hr podinfo-helm \
--target-namespace=default \ --target-namespace=default \
--source=HelmRepository/podinfo \ --source=HelmRepository/podinfo.flux-system \
--chart=podinfo \ --chart=podinfo \
--chart-version=">4.0.0 <5.0.0" --chart-version=">4.0.0 <5.0.0"
- name: flux create helmrelease --source=GitRepository/podinfo - name: flux create helmrelease --source=GitRepository/podinfo
@@ -188,7 +188,8 @@ jobs:
run: | run: |
/tmp/flux create source git flux-system \ /tmp/flux create source git flux-system \
--url=https://github.com/fluxcd/flux2-kustomize-helm-example \ --url=https://github.com/fluxcd/flux2-kustomize-helm-example \
--branch=main --branch=main \
--recurse-submodules
/tmp/flux create kustomization flux-system \ /tmp/flux create kustomization flux-system \
--source=flux-system \ --source=flux-system \
--path=./clusters/staging --path=./clusters/staging

View File

@@ -16,6 +16,26 @@ jobs:
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.16.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
with:
buildkitd-flags: "--debug"
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
- name: Download release notes utility - name: Download release notes utility
env: 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 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

3
.gitignore vendored
View File

@@ -11,6 +11,9 @@
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
# Release
dist/
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
bin/ bin/

View File

@@ -74,3 +74,64 @@ release:
extra_files: extra_files:
- glob: ./output/manifests.tar.gz - glob: ./output/manifests.tar.gz
- glob: ./output/install.yaml - glob: ./output/install.yaml
dockers:
- image_templates:
- 'fluxcd/flux-cli:{{ .Tag }}-amd64'
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-amd64'
dockerfile: Dockerfile
use_buildx: true
goos: linux
goarch: amd64
build_flag_templates:
- "--pull"
- "--build-arg=ARCH=linux/amd64"
- "--label=org.opencontainers.image.created={{ .Date }}"
- "--label=org.opencontainers.image.name={{ .ProjectName }}"
- "--label=org.opencontainers.image.revision={{ .FullCommit }}"
- "--label=org.opencontainers.image.version={{ .Version }}"
- "--label=org.opencontainers.image.source={{ .GitURL }}"
- "--platform=linux/amd64"
- image_templates:
- 'fluxcd/flux-cli:{{ .Tag }}-arm64'
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm64'
dockerfile: Dockerfile
use_buildx: true
goos: linux
goarch: arm64
build_flag_templates:
- "--pull"
- "--build-arg=ARCH=linux/arm64"
- "--label=org.opencontainers.image.created={{ .Date }}"
- "--label=org.opencontainers.image.name={{ .ProjectName }}"
- "--label=org.opencontainers.image.revision={{ .FullCommit }}"
- "--label=org.opencontainers.image.version={{ .Version }}"
- "--label=org.opencontainers.image.source={{ .GitURL }}"
- "--platform=linux/arm64"
- image_templates:
- 'fluxcd/flux-cli:{{ .Tag }}-arm'
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm'
dockerfile: Dockerfile
use_buildx: true
goos: linux
goarch: arm
goarm: 7
build_flag_templates:
- "--pull"
- "--build-arg=ARCH=linux/arm"
- "--label=org.opencontainers.image.created={{ .Date }}"
- "--label=org.opencontainers.image.name={{ .ProjectName }}"
- "--label=org.opencontainers.image.revision={{ .FullCommit }}"
- "--label=org.opencontainers.image.version={{ .Version }}"
- "--label=org.opencontainers.image.source={{ .GitURL }}"
- "--platform=linux/arm/v7"
docker_manifests:
- name_template: 'fluxcd/flux-cli:{{ .Tag }}'
image_templates:
- 'fluxcd/flux-cli:{{ .Tag }}-amd64'
- 'fluxcd/flux-cli:{{ .Tag }}-arm64'
- 'fluxcd/flux-cli:{{ .Tag }}-arm'
- name_template: 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}'
image_templates:
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-amd64'
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm64'
- 'ghcr.io/fluxcd/flux-cli:{{ .Tag }}-arm'

23
Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM alpine:3.13 as builder
RUN apk add --no-cache ca-certificates curl
ARG ARCH=linux/amd64
ARG KUBECTL_VER=1.20.4
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.13 as flux-cli
# Create minimal nsswitch.conf file to prioritize the usage of /etc/hosts over DNS queries.
# https://github.com/gliderlabs/docker-alpine/issues/367#issuecomment-354316460
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf
RUN apk add --no-cache ca-certificates
COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/
COPY --chmod=755 flux /usr/local/bin/
ENTRYPOINT [ "flux" ]

View File

@@ -1,5 +1,6 @@
# Flux version 2 # Flux version 2
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4782/badge)](https://bestpractices.coreinfrastructure.org/projects/4782)
[![e2e](https://github.com/fluxcd/flux2/workflows/e2e/badge.svg)](https://github.com/fluxcd/flux2/actions) [![e2e](https://github.com/fluxcd/flux2/workflows/e2e/badge.svg)](https://github.com/fluxcd/flux2/actions)
[![report](https://goreportcard.com/badge/github.com/fluxcd/flux2)](https://goreportcard.com/report/github.com/fluxcd/flux2) [![report](https://goreportcard.com/badge/github.com/fluxcd/flux2)](https://goreportcard.com/report/github.com/fluxcd/flux2)
[![license](https://img.shields.io/github/license/fluxcd/flux2.svg)](https://github.com/fluxcd/flux2/blob/main/LICENSE) [![license](https://img.shields.io/github/license/fluxcd/flux2.svg)](https://github.com/fluxcd/flux2/blob/main/LICENSE)
@@ -48,6 +49,11 @@ Arch Linux (AUR) packages:
Binaries for macOS, Windows and Linux AMD64/ARM are available to download on the Binaries for macOS, Windows and Linux AMD64/ARM are available to download on the
[release page](https://github.com/fluxcd/flux2/releases). [release page](https://github.com/fluxcd/flux2/releases).
A container image with `kubectl` and `flux` is available on Docker Hub and GitHub:
* `docker.io/fluxcd/flux-cli:<version>`
* `ghcr.io/fluxcd/flux-cli:<version>`
Verify that your cluster satisfies the prerequisites with: Verify that your cluster satisfies the prerequisites with:
```sh ```sh

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
// notificationv1.Alert // notificationv1.Alert

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
// notificationv1.Provider // notificationv1.Provider

View File

@@ -17,26 +17,15 @@ limitations under the License.
package main package main
import ( import (
"context" "crypto/elliptic"
"fmt" "fmt"
"path/filepath" "io/ioutil"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
kus "github.com/fluxcd/flux2/pkg/manifestgen/kustomization"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
"github.com/fluxcd/flux2/pkg/status"
) )
var bootstrapCmd = &cobra.Command{ var bootstrapCmd = &cobra.Command{
@@ -46,21 +35,39 @@ var bootstrapCmd = &cobra.Command{
} }
type bootstrapFlags struct { type bootstrapFlags struct {
version string version string
arch flags.Arch
logLevel flags.LogLevel
branch string
recurseSubmodules bool
manifestsPath string
defaultComponents []string defaultComponents []string
extraComponents []string extraComponents []string
registry string requiredComponents []string
imagePullSecret string
branch string registry string
imagePullSecret string
secretName string
tokenAuth bool
keyAlgorithm flags.PublicKeyAlgorithm
keyRSABits flags.RSAKeyBits
keyECDSACurve flags.ECDSACurve
sshHostname string
caFile string
privateKeyFile string
watchAllNamespaces bool watchAllNamespaces bool
networkPolicy bool networkPolicy bool
manifestsPath string
arch flags.Arch
logLevel flags.LogLevel
requiredComponents []string
tokenAuth bool
clusterDomain string clusterDomain string
tolerationKeys []string tolerationKeys []string
authorName string
authorEmail string
commitMessageAppendix string
} }
const ( const (
@@ -72,17 +79,23 @@ var bootstrapArgs = NewBootstrapFlags()
func init() { func init() {
bootstrapCmd.PersistentFlags().StringVarP(&bootstrapArgs.version, "version", "v", "", bootstrapCmd.PersistentFlags().StringVarP(&bootstrapArgs.version, "version", "v", "",
"toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases") "toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases")
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, "components", rootArgs.defaults.Components, bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, "components", rootArgs.defaults.Components,
"list of components, accepts comma-separated values") "list of components, accepts comma-separated values")
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil, bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil,
"list of components in addition to those supplied or defaulted, accepts comma-separated values") "list of components in addition to those supplied or defaulted, accepts comma-separated values")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd", bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd",
"container registry where the toolkit images are published") "container registry where the toolkit images are published")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.imagePullSecret, "image-pull-secret", "", bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.imagePullSecret, "image-pull-secret", "",
"Kubernetes secret name used for pulling the toolkit images from a private registry") "Kubernetes secret name used for pulling the toolkit images from a private registry")
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.arch, "arch", bootstrapArgs.arch.Description())
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.branch, "branch", bootstrapDefaultBranch, bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.branch, "branch", bootstrapDefaultBranch, "Git branch")
"default branch (for GitHub this must match the default branch setting for the organization)") bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.recurseSubmodules, "recurse-submodules", false,
"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.manifestsPath, "manifests", "", "path to the manifest directory")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.watchAllNamespaces, "watch-all-namespaces", true, bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.watchAllNamespaces, "watch-all-namespaces", true,
"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed") "watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.networkPolicy, "network-policy", true, bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.networkPolicy, "network-policy", true,
@@ -90,12 +103,27 @@ func init() {
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.tokenAuth, "token-auth", false, bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.tokenAuth, "token-auth", false,
"when enabled, the personal access token will be used instead of SSH deploy key") "when enabled, the personal access token will be used instead of SSH deploy key")
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.logLevel, "log-level", bootstrapArgs.logLevel.Description()) bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.logLevel, "log-level", bootstrapArgs.logLevel.Description())
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.manifestsPath, "manifests", "", "path to the manifest directory")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.tolerationKeys, "toleration-keys", nil, bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.tolerationKeys, "toleration-keys", nil,
"list of toleration keys used to schedule the components pods onto nodes with matching taints") "list of toleration keys used to schedule the components pods onto nodes with matching taints")
bootstrapCmd.PersistentFlags().MarkHidden("manifests")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.secretName, "secret-name", rootArgs.defaults.Namespace, "name of the secret the sync credentials can be found in or stored to")
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyAlgorithm, "ssh-key-algorithm", bootstrapArgs.keyAlgorithm.Description())
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyRSABits, "ssh-rsa-bits", bootstrapArgs.keyRSABits.Description())
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.keyECDSACurve, "ssh-ecdsa-curve", bootstrapArgs.keyECDSACurve.Description())
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.sshHostname, "ssh-hostname", "", "SSH hostname, to be used when the SSH host differs from the HTTPS one")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.privateKeyFile, "private-key-file", "", "path to a private key file used for authenticating to the Git SSH server")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorName, "author-name", "Flux", "author name for Git commits")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorEmail, "author-email", "", "author email for Git commits")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'")
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.arch, "arch", bootstrapArgs.arch.Description())
bootstrapCmd.PersistentFlags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64") bootstrapCmd.PersistentFlags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64")
bootstrapCmd.PersistentFlags().MarkHidden("manifests")
rootCmd.AddCommand(bootstrapCmd) rootCmd.AddCommand(bootstrapCmd)
} }
@@ -103,6 +131,9 @@ func NewBootstrapFlags() bootstrapFlags {
return bootstrapFlags{ return bootstrapFlags{
logLevel: flags.LogLevel(rootArgs.defaults.LogLevel), logLevel: flags.LogLevel(rootArgs.defaults.LogLevel),
requiredComponents: []string{"source-controller", "kustomize-controller"}, requiredComponents: []string{"source-controller", "kustomize-controller"},
keyAlgorithm: flags.PublicKeyAlgorithm(sourcesecret.RSAPrivateKeyAlgorithm),
keyRSABits: 2048,
keyECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()},
} }
} }
@@ -110,6 +141,20 @@ func bootstrapComponents() []string {
return append(bootstrapArgs.defaultComponents, bootstrapArgs.extraComponents...) return append(bootstrapArgs.defaultComponents, bootstrapArgs.extraComponents...)
} }
func buildEmbeddedManifestBase() (string, error) {
if !isEmbeddedVersion(bootstrapArgs.version) {
return "", nil
}
tmpBaseDir, err := ioutil.TempDir("", "flux-manifests-")
if err != nil {
return "", err
}
if err := writeEmbeddedManifests(tmpBaseDir); err != nil {
return "", err
}
return tmpBaseDir, nil
}
func bootstrapValidate() error { func bootstrapValidate() error {
components := bootstrapComponents() components := bootstrapComponents()
for _, component := range bootstrapArgs.requiredComponents { for _, component := range bootstrapArgs.requiredComponents {
@@ -125,179 +170,10 @@ func bootstrapValidate() error {
return nil return nil
} }
func generateInstallManifests(targetPath, namespace, tmpDir string, localManifests string) (string, error) { func mapTeamSlice(s []string, defaultPermission string) map[string]string {
if ver, err := getVersion(bootstrapArgs.version); err != nil { m := make(map[string]string, len(s))
return "", err for _, v := range s {
} else { m[v] = defaultPermission
bootstrapArgs.version = ver
} }
return m
manifestsBase := ""
if isEmbeddedVersion(bootstrapArgs.version) {
if err := writeEmbeddedManifests(tmpDir); err != nil {
return "", err
}
manifestsBase = tmpDir
}
opts := install.Options{
BaseURL: localManifests,
Version: bootstrapArgs.version,
Namespace: namespace,
Components: bootstrapComponents(),
Registry: bootstrapArgs.registry,
ImagePullSecret: bootstrapArgs.imagePullSecret,
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
NetworkPolicy: bootstrapArgs.networkPolicy,
LogLevel: bootstrapArgs.logLevel.String(),
NotificationController: rootArgs.defaults.NotificationController,
ManifestFile: rootArgs.defaults.ManifestFile,
Timeout: rootArgs.timeout,
TargetPath: targetPath,
ClusterDomain: bootstrapArgs.clusterDomain,
TolerationKeys: bootstrapArgs.tolerationKeys,
}
if localManifests == "" {
opts.BaseURL = rootArgs.defaults.BaseURL
}
output, err := install.Generate(opts, manifestsBase)
if err != nil {
return "", fmt.Errorf("generating install manifests failed: %w", err)
}
filePath, err := output.WriteFile(tmpDir)
if err != nil {
return "", fmt.Errorf("generating install manifests failed: %w", err)
}
return filePath, nil
}
func applyInstallManifests(ctx context.Context, manifestPath string, components []string) error {
kubectlArgs := []string{"apply", "-f", manifestPath}
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
return fmt.Errorf("install failed: %w", err)
}
kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return fmt.Errorf("install failed: %w", err)
}
statusChecker, err := status.NewStatusChecker(kubeConfig, time.Second, rootArgs.timeout, logger)
if err != nil {
return fmt.Errorf("install failed: %w", err)
}
componentRefs, err := buildComponentObjectRefs(components...)
if err != nil {
return fmt.Errorf("install failed: %w", err)
}
logger.Waitingf("verifying installation")
if err := statusChecker.Assess(componentRefs...); err != nil {
return fmt.Errorf("install failed")
}
return nil
}
func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir string, interval time.Duration) (string, error) {
opts := sync.Options{
Name: name,
Namespace: namespace,
URL: url,
Branch: branch,
Interval: interval,
Secret: namespace,
TargetPath: targetPath,
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
}
manifest, err := sync.Generate(opts)
if err != nil {
return "", fmt.Errorf("generating install manifests failed: %w", err)
}
output, err := manifest.WriteFile(tmpDir)
if err != nil {
return "", err
}
outputDir := filepath.Dir(output)
kusOpts := kus.MakeDefaultOptions()
kusOpts.BaseDir = tmpDir
kusOpts.TargetPath = filepath.Dir(manifest.Path)
kustomization, err := kus.Generate(kusOpts)
if err != nil {
return "", err
}
if _, err = kustomization.WriteFile(tmpDir); err != nil {
return "", err
}
return outputDir, nil
}
func applySyncManifests(ctx context.Context, kubeClient client.Client, name, namespace, manifestsPath string) error {
kubectlArgs := []string{"apply", "-k", manifestsPath}
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
return err
}
logger.Waitingf("waiting for cluster sync")
var gitRepository sourcev1.GitRepository
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isGitRepositoryReady(ctx, kubeClient, types.NamespacedName{Name: name, Namespace: namespace}, &gitRepository)); err != nil {
return err
}
var kustomization kustomizev1.Kustomization
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isKustomizationReady(ctx, kubeClient, types.NamespacedName{Name: name, Namespace: namespace}, &kustomization)); err != nil {
return err
}
return nil
}
func shouldInstallManifests(ctx context.Context, kubeClient client.Client, namespace string) bool {
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: namespace,
}
var kustomization kustomizev1.Kustomization
if err := kubeClient.Get(ctx, namespacedName, &kustomization); err != nil {
return true
}
return kustomization.Status.LastAppliedRevision == ""
}
func shouldCreateDeployKey(ctx context.Context, kubeClient client.Client, namespace string) bool {
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: namespace,
}
var existing corev1.Secret
if err := kubeClient.Get(ctx, namespacedName, &existing); err != nil {
return true
}
return false
}
func checkIfBootstrapPathDiffers(ctx context.Context, kubeClient client.Client, namespace string, path string) (string, bool) {
namespacedName := types.NamespacedName{
Name: namespace,
Namespace: namespace,
}
var fluxSystemKustomization kustomizev1.Kustomization
err := kubeClient.Get(ctx, namespacedName, &fluxSystemKustomization)
if err != nil {
return "", false
}
if fluxSystemKustomization.Spec.Path == path {
return "", false
}
return fluxSystemKustomization.Spec.Path, true
} }

256
cmd/flux/bootstrap_git.go Normal file
View File

@@ -0,0 +1,256 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"io/ioutil"
"net/url"
"os"
"strings"
"time"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"github.com/fluxcd/flux2/internal/bootstrap"
"github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
)
var bootstrapGitCmd = &cobra.Command{
Use: "git",
Short: "Bootstrap toolkit components in a Git repository",
Long: `The bootstrap git command commits the toolkit components manifests to the
branch of a Git repository. It then configures the target cluster to synchronize with
the repository. If the toolkit components are present on the cluster, the bootstrap
command will perform an upgrade if needed.`,
Example: ` # Run bootstrap for a Git repository and authenticate with your SSH agent
flux bootstrap git --url=ssh://git@example.com/repository.git
# Run bootstrap for a Git repository and authenticate using a password
flux bootstrap git --url=https://example.com/repository.git --password=<password>
# Run bootstrap for a Git repository with a passwordless private key
flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key>
`,
RunE: bootstrapGitCmdRun,
}
type gitFlags struct {
url string
interval time.Duration
path flags.SafeRelativePath
username string
password string
}
var gitArgs gitFlags
func init() {
bootstrapGitCmd.Flags().StringVar(&gitArgs.url, "url", "", "Git repository URL")
bootstrapGitCmd.Flags().DurationVar(&gitArgs.interval, "interval", time.Minute, "sync interval")
bootstrapGitCmd.Flags().Var(&gitArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
bootstrapGitCmd.Flags().StringVarP(&gitArgs.username, "username", "u", "git", "basic authentication username")
bootstrapGitCmd.Flags().StringVarP(&gitArgs.password, "password", "p", "", "basic authentication password")
bootstrapCmd.AddCommand(bootstrapGitCmd)
}
func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
if err := bootstrapValidate(); err != nil {
return err
}
repositoryURL, err := url.Parse(gitArgs.url)
if err != nil {
return err
}
gitAuth, err := transportForURL(repositoryURL)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
// Manifest base
if ver, err := getVersion(bootstrapArgs.version); err == nil {
bootstrapArgs.version = ver
}
manifestsBase, err := buildEmbeddedManifestBase()
if err != nil {
return err
}
defer os.RemoveAll(manifestsBase)
// Lazy go-git repository
tmpDir, err := ioutil.TempDir("", "flux-bootstrap-")
if err != nil {
return fmt.Errorf("failed to create temporary working dir: %w", err)
}
defer os.RemoveAll(tmpDir)
gitClient := gogit.New(tmpDir, gitAuth)
// Install manifest config
installOptions := install.Options{
BaseURL: rootArgs.defaults.BaseURL,
Version: bootstrapArgs.version,
Namespace: rootArgs.namespace,
Components: bootstrapComponents(),
Registry: bootstrapArgs.registry,
ImagePullSecret: bootstrapArgs.imagePullSecret,
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
NetworkPolicy: bootstrapArgs.networkPolicy,
LogLevel: bootstrapArgs.logLevel.String(),
NotificationController: rootArgs.defaults.NotificationController,
ManifestFile: rootArgs.defaults.ManifestFile,
Timeout: rootArgs.timeout,
TargetPath: gitArgs.path.String(),
ClusterDomain: bootstrapArgs.clusterDomain,
TolerationKeys: bootstrapArgs.tolerationKeys,
}
if customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != "" {
installOptions.BaseURL = customBaseURL
}
// Source generation and secret config
secretOpts := sourcesecret.Options{
Name: bootstrapArgs.secretName,
Namespace: rootArgs.namespace,
TargetPath: gitArgs.path.String(),
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
}
if bootstrapArgs.tokenAuth {
secretOpts.Username = gitArgs.username
secretOpts.Password = gitArgs.password
if bootstrapArgs.caFile != "" {
secretOpts.CAFilePath = bootstrapArgs.caFile
}
// 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.RSAKeyBits = int(bootstrapArgs.keyRSABits)
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
// Configure repository URL to match auth config for sync.
repositoryURL.User = url.User(gitArgs.username)
repositoryURL.Scheme = "ssh"
repositoryURL.Host = repositoryURL.Hostname()
if bootstrapArgs.sshHostname != "" {
repositoryURL.Host = bootstrapArgs.sshHostname
}
if bootstrapArgs.privateKeyFile != "" {
secretOpts.PrivateKeyPath = bootstrapArgs.privateKeyFile
}
// Configure last as it depends on the config above.
secretOpts.SSHHostname = repositoryURL.Host
}
// Sync manifest config
syncOpts := sync.Options{
Interval: gitArgs.interval,
Name: rootArgs.namespace,
Namespace: rootArgs.namespace,
URL: repositoryURL.String(),
Branch: bootstrapArgs.branch,
Secret: bootstrapArgs.secretName,
TargetPath: gitArgs.path.String(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
GitImplementation: sourceGitArgs.gitImplementation.String(),
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
}
// Bootstrap config
bootstrapOpts := []bootstrap.GitOption{
bootstrap.WithRepositoryURL(gitArgs.url),
bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
bootstrap.WithLogger(logger),
}
// Setup bootstrapper with constructed configs
b, err := bootstrap.NewPlainGitProvider(gitClient, kubeClient, bootstrapOpts...)
if err != nil {
return err
}
// Run
return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
}
// transportForURL constructs a transport.AuthMethod based on the scheme
// of the given URL and the configured flags. If the protocol equals
// "ssh" but no private key is configured, authentication using the local
// SSH-agent is attempted.
func transportForURL(u *url.URL) (transport.AuthMethod, error) {
switch u.Scheme {
case "https":
return &http.BasicAuth{
Username: gitArgs.username,
Password: gitArgs.password,
}, nil
case "ssh":
if bootstrapArgs.privateKeyFile != "" {
return ssh.NewPublicKeysFromFile(u.User.Username(), bootstrapArgs.privateKeyFile, "")
}
return nil, nil
default:
return nil, fmt.Errorf("scheme %q is not supported", u.Scheme)
}
}
func promptPublicKey(ctx context.Context, secret corev1.Secret, _ sourcesecret.Options) error {
ppk, ok := secret.StringData[sourcesecret.PublicKeySecretKey]
if !ok {
return nil
}
logger.Successf("public key: %s", strings.TrimSpace(ppk))
prompt := promptui.Prompt{
Label: "Please give the key access to your repository",
IsConfirm: true,
}
_, err := prompt.Run()
if err != nil {
return fmt.Errorf("aborting")
}
return nil
}

View File

@@ -20,20 +20,20 @@ import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/url"
"os" "os"
"path"
"path/filepath"
"time" "time"
"github.com/fluxcd/pkg/git" "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/internal/bootstrap"
"github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/internal/bootstrap/provider"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
) )
var bootstrapGitHubCmd = &cobra.Command{ var bootstrapGitHubCmd = &cobra.Command{
@@ -71,19 +71,21 @@ the bootstrap command will perform an upgrade if needed.`,
} }
type githubFlags struct { type githubFlags struct {
owner string owner string
repository string repository string
interval time.Duration interval time.Duration
personal bool personal bool
private bool private bool
hostname string hostname string
path flags.SafeRelativePath path flags.SafeRelativePath
teams []string teams []string
sshHostname string readWriteKey bool
} }
const ( const (
ghDefaultPermission = "maintain" ghDefaultPermission = "maintain"
ghDefaultDomain = "github.com"
ghTokenEnvVar = "GITHUB_TOKEN"
) )
var githubArgs githubFlags var githubArgs githubFlags
@@ -95,17 +97,17 @@ func init() {
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.personal, "personal", false, "if true, the owner is assumed to be a GitHub user; otherwise an org") bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.personal, "personal", false, "if true, the owner is assumed to be a GitHub user; otherwise an org")
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.private, "private", true, "if true, the repository is assumed to be private") bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.private, "private", true, "if true, the repository is assumed to be private")
bootstrapGitHubCmd.Flags().DurationVar(&githubArgs.interval, "interval", time.Minute, "sync interval") bootstrapGitHubCmd.Flags().DurationVar(&githubArgs.interval, "interval", time.Minute, "sync interval")
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.hostname, "hostname", git.GitHubDefaultHostname, "GitHub hostname") bootstrapGitHubCmd.Flags().StringVar(&githubArgs.hostname, "hostname", ghDefaultDomain, "GitHub hostname")
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.sshHostname, "ssh-hostname", "", "GitHub SSH hostname, to be used when the SSH host differs from the HTTPS one")
bootstrapGitHubCmd.Flags().Var(&githubArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path") bootstrapGitHubCmd.Flags().Var(&githubArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions")
bootstrapCmd.AddCommand(bootstrapGitHubCmd) bootstrapCmd.AddCommand(bootstrapGitHubCmd)
} }
func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
ghToken := os.Getenv(git.GitHubTokenName) ghToken := os.Getenv(ghTokenEnvVar)
if ghToken == "" { if ghToken == "" {
return fmt.Errorf("%s environment variable not found", git.GitHubTokenName) return fmt.Errorf("%s environment variable not found", ghTokenEnvVar)
} }
if err := bootstrapValidate(); err != nil { if err := bootstrapValidate(); err != nil {
@@ -120,205 +122,126 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
usedPath, bootstrapPathDiffers := checkIfBootstrapPathDiffers( // Manifest base
ctx, if ver, err := getVersion(bootstrapArgs.version); err == nil {
kubeClient, bootstrapArgs.version = ver
rootArgs.namespace,
filepath.ToSlash(githubArgs.path.String()),
)
if bootstrapPathDiffers {
return fmt.Errorf("cluster already bootstrapped to %v path", usedPath)
} }
manifestsBase, err := buildEmbeddedManifestBase()
if err != nil {
return err
}
defer os.RemoveAll(manifestsBase)
repository, err := git.NewRepository( // Build GitHub provider
githubArgs.repository, providerCfg := provider.Config{
githubArgs.owner, Provider: provider.GitProviderGitHub,
githubArgs.hostname, Hostname: githubArgs.hostname,
ghToken, Token: ghToken,
"flux", }
githubArgs.owner+"@users.noreply.github.com", providerClient, err := provider.BuildGitProvider(providerCfg)
)
if err != nil { if err != nil {
return err return err
} }
if githubArgs.sshHostname != "" { // Lazy go-git repository
repository.SSHHost = githubArgs.sshHostname tmpDir, err := ioutil.TempDir("", "flux-bootstrap-")
}
provider := &git.GithubProvider{
IsPrivate: githubArgs.private,
IsPersonal: githubArgs.personal,
}
tmpDir, err := ioutil.TempDir("", rootArgs.namespace)
if err != nil { if err != nil {
return err return fmt.Errorf("failed to create temporary working dir: %w", err)
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
gitClient := gogit.New(tmpDir, &http.BasicAuth{
Username: githubArgs.owner,
Password: ghToken,
})
// create GitHub repository if doesn't exists // Install manifest config
logger.Actionf("connecting to %s", githubArgs.hostname) installOptions := install.Options{
changed, err := provider.CreateRepository(ctx, repository) BaseURL: rootArgs.defaults.BaseURL,
if err != nil { Version: bootstrapArgs.version,
return err Namespace: rootArgs.namespace,
Components: bootstrapComponents(),
Registry: bootstrapArgs.registry,
ImagePullSecret: bootstrapArgs.imagePullSecret,
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
NetworkPolicy: bootstrapArgs.networkPolicy,
LogLevel: bootstrapArgs.logLevel.String(),
NotificationController: rootArgs.defaults.NotificationController,
ManifestFile: rootArgs.defaults.ManifestFile,
Timeout: rootArgs.timeout,
TargetPath: githubArgs.path.String(),
ClusterDomain: bootstrapArgs.clusterDomain,
TolerationKeys: bootstrapArgs.tolerationKeys,
} }
if changed { if customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != "" {
logger.Successf("repository created") installOptions.BaseURL = customBaseURL
} }
withErrors := false // Source generation and secret config
// add teams to org repository
if !githubArgs.personal {
for _, team := range githubArgs.teams {
if changed, err := provider.AddTeam(ctx, repository, team, ghDefaultPermission); err != nil {
logger.Failuref(err.Error())
withErrors = true
} else if changed {
logger.Successf("%s team access granted", team)
}
}
}
// clone repository and checkout the main branch
if err := repository.Checkout(ctx, bootstrapArgs.branch, tmpDir); err != nil {
return err
}
logger.Successf("repository cloned")
// generate install manifests
logger.Generatef("generating manifests")
installManifest, err := generateInstallManifests(
githubArgs.path.String(),
rootArgs.namespace,
tmpDir,
bootstrapArgs.manifestsPath,
)
if err != nil {
return err
}
// stage install manifests
changed, err = repository.Commit(
ctx,
path.Join(githubArgs.path.String(), rootArgs.namespace),
fmt.Sprintf("Add flux %s components manifests", bootstrapArgs.version),
)
if err != nil {
return err
}
// push install manifests
if changed {
if err := repository.Push(ctx); err != nil {
return err
}
logger.Successf("components manifests pushed")
} else {
logger.Successf("components are up to date")
}
// determine if repository synchronization is working
isInstall := shouldInstallManifests(ctx, kubeClient, rootArgs.namespace)
if isInstall {
// apply install manifests
logger.Actionf("installing components in %s namespace", rootArgs.namespace)
if err := applyInstallManifests(ctx, installManifest, bootstrapComponents()); err != nil {
return err
}
logger.Successf("install completed")
}
repoURL := repository.GetSSH()
secretOpts := sourcesecret.Options{ secretOpts := sourcesecret.Options{
Name: rootArgs.namespace, Name: bootstrapArgs.secretName,
Namespace: rootArgs.namespace, Namespace: rootArgs.namespace,
TargetPath: githubArgs.path.String(),
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
} }
if bootstrapArgs.tokenAuth { if bootstrapArgs.tokenAuth {
// Setup HTTPS token auth
repoURL = repository.GetURL()
secretOpts.Username = "git" secretOpts.Username = "git"
secretOpts.Password = ghToken secretOpts.Password = ghToken
} else if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
// Setup SSH auth if bootstrapArgs.caFile != "" {
u, err := url.Parse(repoURL) secretOpts.CAFilePath = bootstrapArgs.caFile
if err != nil {
return fmt.Errorf("git URL parse failed: %w", err)
} }
secretOpts.SSHHostname = u.Host } else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.RSAPrivateKeyAlgorithm secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.RSAKeyBits = 2048 secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
} secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
secretOpts.SSHHostname = githubArgs.hostname
secret, err := sourcesecret.Generate(secretOpts) if bootstrapArgs.sshHostname != "" {
if err != nil { secretOpts.SSHHostname = bootstrapArgs.sshHostname
return err
}
var s corev1.Secret
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
return err
}
if len(s.StringData) > 0 {
logger.Actionf("configuring deploy key")
if err := upsertSecret(ctx, kubeClient, s); err != nil {
return err
}
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
keyName := "flux"
if githubArgs.path != "" {
keyName = fmt.Sprintf("flux-%s", githubArgs.path)
}
if changed, err := provider.AddDeployKey(ctx, repository, ppk, keyName); err != nil {
return err
} else if changed {
logger.Successf("deploy key configured")
}
} }
} }
// configure repository synchronization // Sync manifest config
logger.Actionf("generating sync manifests") syncOpts := sync.Options{
syncManifests, err := generateSyncManifests( Interval: githubArgs.interval,
repoURL, Name: rootArgs.namespace,
bootstrapArgs.branch, Namespace: rootArgs.namespace,
rootArgs.namespace, Branch: bootstrapArgs.branch,
rootArgs.namespace, Secret: bootstrapArgs.secretName,
filepath.ToSlash(githubArgs.path.String()), TargetPath: githubArgs.path.String(),
tmpDir, ManifestFile: sync.MakeDefaultOptions().ManifestFile,
githubArgs.interval, GitImplementation: sourceGitArgs.gitImplementation.String(),
) RecurseSubmodules: bootstrapArgs.recurseSubmodules,
}
// Bootstrap config
bootstrapOpts := []bootstrap.GitProviderOption{
bootstrap.WithProviderRepository(githubArgs.owner, githubArgs.repository, githubArgs.personal),
bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithBootstrapTransportType("https"),
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithProviderTeamPermissions(mapTeamSlice(githubArgs.teams, ghDefaultPermission)),
bootstrap.WithReadWriteKeyPermissions(githubArgs.readWriteKey),
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
bootstrap.WithLogger(logger),
}
if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
}
if bootstrapArgs.tokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https"))
}
if !githubArgs.private {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
}
// Setup bootstrapper with constructed configs
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil { if err != nil {
return err return err
} }
// commit and push manifests // Run
if changed, err = repository.Commit( return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
ctx,
path.Join(githubArgs.path.String(), rootArgs.namespace),
fmt.Sprintf("Add flux %s sync manifests", bootstrapArgs.version),
); err != nil {
return err
} else if changed {
if err := repository.Push(ctx); err != nil {
return err
}
logger.Successf("sync manifests pushed")
}
// apply manifests and waiting for sync
logger.Actionf("applying sync manifests")
if err := applySyncManifests(ctx, kubeClient, rootArgs.namespace, rootArgs.namespace, syncManifests); err != nil {
return err
}
if withErrors {
return fmt.Errorf("bootstrap completed with errors")
}
logger.Successf("bootstrap finished")
return nil
} }

View File

@@ -20,22 +20,22 @@ import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/url"
"os" "os"
"path"
"path/filepath"
"regexp" "regexp"
"strings"
"time" "time"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/git"
"github.com/fluxcd/flux2/internal/bootstrap"
"github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/internal/bootstrap/provider"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
) )
var bootstrapGitLabCmd = &cobra.Command{ var bootstrapGitLabCmd = &cobra.Command{
@@ -70,18 +70,22 @@ the bootstrap command will perform an upgrade if needed.`,
} }
const ( const (
gitlabProjectRegex = `\A[[:alnum:]\x{00A9}-\x{1f9ff}_][[:alnum:]\p{Pd}\x{00A9}-\x{1f9ff}_\.]*\z` glDefaultPermission = "maintain"
glDefaultDomain = "gitlab.com"
glTokenEnvVar = "GITLAB_TOKEN"
gitlabProjectRegex = `\A[[:alnum:]\x{00A9}-\x{1f9ff}_][[:alnum:]\p{Pd}\x{00A9}-\x{1f9ff}_\.]*\z`
) )
type gitlabFlags struct { type gitlabFlags struct {
owner string owner string
repository string repository string
interval time.Duration interval time.Duration
personal bool personal bool
private bool private bool
hostname string hostname string
sshHostname string path flags.SafeRelativePath
path flags.SafeRelativePath teams []string
readWriteKey bool
} }
var gitlabArgs gitlabFlags var gitlabArgs gitlabFlags
@@ -89,29 +93,29 @@ var gitlabArgs gitlabFlags
func init() { func init() {
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.owner, "owner", "", "GitLab user or group name") bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.owner, "owner", "", "GitLab user or group name")
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.repository, "repository", "", "GitLab repository name") bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.repository, "repository", "", "GitLab repository name")
bootstrapGitLabCmd.Flags().StringArrayVar(&gitlabArgs.teams, "team", []string{}, "GitLab teams to be given maintainer access")
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.personal, "personal", false, "if true, the owner is assumed to be a GitLab user; otherwise a group") bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.personal, "personal", false, "if true, the owner is assumed to be a GitLab user; otherwise a group")
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.private, "private", true, "if true, the repository is assumed to be private") bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.private, "private", true, "if true, the repository is assumed to be private")
bootstrapGitLabCmd.Flags().DurationVar(&gitlabArgs.interval, "interval", time.Minute, "sync interval") bootstrapGitLabCmd.Flags().DurationVar(&gitlabArgs.interval, "interval", time.Minute, "sync interval")
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.hostname, "hostname", git.GitLabDefaultHostname, "GitLab hostname") bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.hostname, "hostname", glDefaultDomain, "GitLab hostname")
bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.sshHostname, "ssh-hostname", "", "GitLab SSH hostname, to be used when the SSH host differs from the HTTPS one")
bootstrapGitLabCmd.Flags().Var(&gitlabArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path") bootstrapGitLabCmd.Flags().Var(&gitlabArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions")
bootstrapCmd.AddCommand(bootstrapGitLabCmd) bootstrapCmd.AddCommand(bootstrapGitLabCmd)
} }
func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
glToken := os.Getenv(git.GitLabTokenName) glToken := os.Getenv(glTokenEnvVar)
if glToken == "" { if glToken == "" {
return fmt.Errorf("%s environment variable not found", git.GitLabTokenName) return fmt.Errorf("%s environment variable not found", glTokenEnvVar)
} }
projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, gitlabArgs.repository) if projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, gitlabArgs.repository); err != nil || !projectNameIsValid {
if err != nil { if err == nil {
err = fmt.Errorf("%s is an invalid project name for gitlab.\nIt can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.", gitlabArgs.repository)
}
return err return err
} }
if !projectNameIsValid {
return fmt.Errorf("%s is an invalid project name for gitlab.\nIt can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.", gitlabArgs.repository)
}
if err := bootstrapValidate(); err != nil { if err := bootstrapValidate(); err != nil {
return err return err
@@ -125,183 +129,135 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
usedPath, bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, rootArgs.namespace, filepath.ToSlash(gitlabArgs.path.String())) // Manifest base
if ver, err := getVersion(bootstrapArgs.version); err == nil {
if bootstrapPathDiffers { bootstrapArgs.version = ver
return fmt.Errorf("cluster already bootstrapped to %v path", usedPath)
} }
manifestsBase, err := buildEmbeddedManifestBase()
if err != nil {
return err
}
defer os.RemoveAll(manifestsBase)
repository, err := git.NewRepository( // Build GitLab provider
gitlabArgs.repository, providerCfg := provider.Config{
gitlabArgs.owner, Provider: provider.GitProviderGitLab,
gitlabArgs.hostname, Hostname: gitlabArgs.hostname,
glToken, Token: glToken,
"flux", }
gitlabArgs.owner+"@users.noreply.gitlab.com", // Workaround for: https://github.com/fluxcd/go-git-providers/issues/55
) if hostname := providerCfg.Hostname; hostname != glDefaultDomain &&
!strings.HasPrefix(hostname, "https://") &&
!strings.HasPrefix(hostname, "http://") {
providerCfg.Hostname = "https://" + providerCfg.Hostname
}
providerClient, err := provider.BuildGitProvider(providerCfg)
if err != nil { if err != nil {
return err return err
} }
if gitlabArgs.sshHostname != "" { // Lazy go-git repository
repository.SSHHost = gitlabArgs.sshHostname tmpDir, err := ioutil.TempDir("", "flux-bootstrap-")
}
tmpDir, err := ioutil.TempDir("", rootArgs.namespace)
if err != nil { if err != nil {
return err return fmt.Errorf("failed to create temporary working dir: %w", err)
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
gitClient := gogit.New(tmpDir, &http.BasicAuth{
Username: gitlabArgs.owner,
Password: glToken,
})
provider := &git.GitLabProvider{ // Install manifest config
IsPrivate: gitlabArgs.private, installOptions := install.Options{
IsPersonal: gitlabArgs.personal, BaseURL: rootArgs.defaults.BaseURL,
Version: bootstrapArgs.version,
Namespace: rootArgs.namespace,
Components: bootstrapComponents(),
Registry: bootstrapArgs.registry,
ImagePullSecret: bootstrapArgs.imagePullSecret,
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
NetworkPolicy: bootstrapArgs.networkPolicy,
LogLevel: bootstrapArgs.logLevel.String(),
NotificationController: rootArgs.defaults.NotificationController,
ManifestFile: rootArgs.defaults.ManifestFile,
Timeout: rootArgs.timeout,
TargetPath: gitlabArgs.path.String(),
ClusterDomain: bootstrapArgs.clusterDomain,
TolerationKeys: bootstrapArgs.tolerationKeys,
}
if customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != "" {
installOptions.BaseURL = customBaseURL
} }
// create GitLab project if doesn't exists // Source generation and secret config
logger.Actionf("connecting to %s", gitlabArgs.hostname)
changed, err := provider.CreateRepository(ctx, repository)
if err != nil {
return err
}
if changed {
logger.Successf("repository created")
}
// clone repository and checkout the master branch
if err := repository.Checkout(ctx, bootstrapArgs.branch, tmpDir); err != nil {
return err
}
logger.Successf("repository cloned")
// generate install manifests
logger.Generatef("generating manifests")
installManifest, err := generateInstallManifests(
gitlabArgs.path.String(),
rootArgs.namespace,
tmpDir,
bootstrapArgs.manifestsPath,
)
if err != nil {
return err
}
// stage install manifests
changed, err = repository.Commit(
ctx,
path.Join(gitlabArgs.path.String(), rootArgs.namespace),
fmt.Sprintf("Add flux %s components manifests", bootstrapArgs.version),
)
if err != nil {
return err
}
// push install manifests
if changed {
if err := repository.Push(ctx); err != nil {
return err
}
logger.Successf("components manifests pushed")
} else {
logger.Successf("components are up to date")
}
// determine if repository synchronization is working
isInstall := shouldInstallManifests(ctx, kubeClient, rootArgs.namespace)
if isInstall {
// apply install manifests
logger.Actionf("installing components in %s namespace", rootArgs.namespace)
if err := applyInstallManifests(ctx, installManifest, bootstrapComponents()); err != nil {
return err
}
logger.Successf("install completed")
}
repoURL := repository.GetSSH()
secretOpts := sourcesecret.Options{ secretOpts := sourcesecret.Options{
Name: rootArgs.namespace, Name: bootstrapArgs.secretName,
Namespace: rootArgs.namespace, Namespace: rootArgs.namespace,
TargetPath: gitlabArgs.path.String(),
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
} }
if bootstrapArgs.tokenAuth { if bootstrapArgs.tokenAuth {
// Setup HTTPS token auth
repoURL = repository.GetURL()
secretOpts.Username = "git" secretOpts.Username = "git"
secretOpts.Password = glToken secretOpts.Password = glToken
} else if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
// Setup SSH auth if bootstrapArgs.caFile != "" {
u, err := url.Parse(repoURL) secretOpts.CAFilePath = bootstrapArgs.caFile
if err != nil {
return fmt.Errorf("git URL parse failed: %w", err)
} }
secretOpts.SSHHostname = u.Host } else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.RSAPrivateKeyAlgorithm secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.RSAKeyBits = 2048 secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
} secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
secretOpts.SSHHostname = gitlabArgs.hostname
secret, err := sourcesecret.Generate(secretOpts) if bootstrapArgs.privateKeyFile != "" {
if err != nil { secretOpts.PrivateKeyPath = bootstrapArgs.privateKeyFile
return err
}
var s corev1.Secret
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
return err
}
if len(s.StringData) > 0 {
logger.Actionf("configuring deploy key")
if err := upsertSecret(ctx, kubeClient, s); err != nil {
return err
} }
if bootstrapArgs.sshHostname != "" {
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok { secretOpts.SSHHostname = bootstrapArgs.sshHostname
keyName := "flux"
if gitlabArgs.path != "" {
keyName = fmt.Sprintf("flux-%s", gitlabArgs.path)
}
if changed, err := provider.AddDeployKey(ctx, repository, ppk, keyName); err != nil {
return err
} else if changed {
logger.Successf("deploy key configured")
}
} }
} }
// configure repository synchronization // Sync manifest config
logger.Actionf("generating sync manifests") syncOpts := sync.Options{
syncManifests, err := generateSyncManifests( Interval: gitlabArgs.interval,
repoURL, Name: rootArgs.namespace,
bootstrapArgs.branch, Namespace: rootArgs.namespace,
rootArgs.namespace, Branch: bootstrapArgs.branch,
rootArgs.namespace, Secret: bootstrapArgs.secretName,
filepath.ToSlash(gitlabArgs.path.String()), TargetPath: gitlabArgs.path.String(),
tmpDir, ManifestFile: sync.MakeDefaultOptions().ManifestFile,
gitlabArgs.interval, GitImplementation: sourceGitArgs.gitImplementation.String(),
) RecurseSubmodules: bootstrapArgs.recurseSubmodules,
}
// Bootstrap config
bootstrapOpts := []bootstrap.GitProviderOption{
bootstrap.WithProviderRepository(gitlabArgs.owner, gitlabArgs.repository, gitlabArgs.personal),
bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithBootstrapTransportType("https"),
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithProviderTeamPermissions(mapTeamSlice(gitlabArgs.teams, glDefaultPermission)),
bootstrap.WithReadWriteKeyPermissions(gitlabArgs.readWriteKey),
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
bootstrap.WithLogger(logger),
}
if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
}
if bootstrapArgs.tokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https"))
}
if !gitlabArgs.private {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
}
// Setup bootstrapper with constructed configs
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil { if err != nil {
return err return err
} }
// commit and push manifests // Run
if changed, err = repository.Commit( return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
ctx,
path.Join(gitlabArgs.path.String(), rootArgs.namespace),
fmt.Sprintf("Add flux %s sync manifests", bootstrapArgs.version),
); err != nil {
return err
} else if changed {
if err := repository.Push(ctx); err != nil {
return err
}
logger.Successf("sync manifests pushed")
}
// apply manifests and waiting for sync
logger.Actionf("applying sync manifests")
if err := applySyncManifests(ctx, kubeClient, rootArgs.namespace, rootArgs.namespace, syncManifests); err != nil {
return err
}
logger.Successf("bootstrap finished")
return nil
} }

View File

@@ -91,6 +91,12 @@ var createHelmReleaseCmd = &cobra.Command{
--source=HelmRepository/podinfo \ --source=HelmRepository/podinfo \
--chart=podinfo --chart=podinfo
# Create a HelmRelease using a source from a different namespace
flux create hr podinfo \
--namespace=default \
--source=HelmRepository/podinfo.flux-system \
--chart=podinfo
# Create a HelmRelease definition on disk without applying it on the cluster # Create a HelmRelease definition on disk without applying it on the cluster
flux create hr podinfo \ flux create hr podinfo \
--source=HelmRepository/podinfo \ --source=HelmRepository/podinfo \
@@ -164,8 +170,9 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
Chart: helmReleaseArgs.chart, Chart: helmReleaseArgs.chart,
Version: helmReleaseArgs.chartVersion, Version: helmReleaseArgs.chartVersion,
SourceRef: helmv2.CrossNamespaceObjectReference{ SourceRef: helmv2.CrossNamespaceObjectReference{
Kind: helmReleaseArgs.source.Kind, Kind: helmReleaseArgs.source.Kind,
Name: helmReleaseArgs.source.Name, Name: helmReleaseArgs.source.Name,
Namespace: helmReleaseArgs.source.Namespace,
}, },
}, },
}, },

View File

@@ -46,7 +46,7 @@ var createKsCmd = &cobra.Command{
Long: "The kustomization source create command generates a Kustomize resource for a given source.", Long: "The kustomization source create command generates a Kustomize resource for a given source.",
Example: ` # Create a Kustomization resource from a source at a given path Example: ` # Create a Kustomization resource from a source at a given path
flux create kustomization contour \ flux create kustomization contour \
--source=contour \ --source=GitRepository/contour \
--path="./examples/contour/" \ --path="./examples/contour/" \
--prune=true \ --prune=true \
--interval=10m \ --interval=10m \
@@ -58,7 +58,16 @@ var createKsCmd = &cobra.Command{
# Create a Kustomization resource that depends on the previous one # Create a Kustomization resource that depends on the previous one
flux create kustomization webapp \ flux create kustomization webapp \
--depends-on=contour \ --depends-on=contour \
--source=webapp \ --source=GitRepository/webapp \
--path="./deploy/overlays/dev" \
--prune=true \
--interval=5m \
--validation=client
# Create a Kustomization using a source from a different namespace
flux create kustomization podinfo \
--namespace=default \
--source=GitRepository/podinfo.flux-system \
--path="./deploy/overlays/dev" \ --path="./deploy/overlays/dev" \
--prune=true \ --prune=true \
--interval=5m \ --interval=5m \
@@ -145,8 +154,9 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
Path: filepath.ToSlash(kustomizationArgs.path.String()), Path: filepath.ToSlash(kustomizationArgs.path.String()),
Prune: kustomizationArgs.prune, Prune: kustomizationArgs.prune,
SourceRef: kustomizev1.CrossNamespaceSourceReference{ SourceRef: kustomizev1.CrossNamespaceSourceReference{
Kind: kustomizationArgs.source.Kind, Kind: kustomizationArgs.source.Kind,
Name: kustomizationArgs.source.Name, Name: kustomizationArgs.source.Name,
Namespace: kustomizationArgs.source.Namespace,
}, },
Suspend: false, Suspend: false,
Validation: kustomizationArgs.validation, Validation: kustomizationArgs.validation,

View File

@@ -56,6 +56,7 @@ type sourceGitFlags struct {
gitImplementation flags.GitImplementation gitImplementation flags.GitImplementation
caFile string caFile string
privateKeyFile string privateKeyFile string
recurseSubmodules bool
} }
var createSourceGitCmd = &cobra.Command{ var createSourceGitCmd = &cobra.Command{
@@ -122,8 +123,10 @@ func init() {
createSourceGitCmd.Flags().Var(&sourceGitArgs.keyECDSACurve, "ssh-ecdsa-curve", sourceGitArgs.keyECDSACurve.Description()) createSourceGitCmd.Flags().Var(&sourceGitArgs.keyECDSACurve, "ssh-ecdsa-curve", sourceGitArgs.keyECDSACurve.Description())
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.secretRef, "secret-ref", "", "the name of an existing secret containing SSH or basic credentials") createSourceGitCmd.Flags().StringVar(&sourceGitArgs.secretRef, "secret-ref", "", "the name of an existing secret containing SSH or basic credentials")
createSourceGitCmd.Flags().Var(&sourceGitArgs.gitImplementation, "git-implementation", sourceGitArgs.gitImplementation.Description()) createSourceGitCmd.Flags().Var(&sourceGitArgs.gitImplementation, "git-implementation", sourceGitArgs.gitImplementation.Description())
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates, requires libgit2") createSourceGitCmd.Flags().StringVar(&sourceGitArgs.caFile, "ca-file", "", "path to TLS CA file used for validating self-signed certificates")
createSourceGitCmd.Flags().StringVar(&sourceGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server") createSourceGitCmd.Flags().StringVar(&sourceGitArgs.privateKeyFile, "private-key-file", "", "path to a passwordless private key file used for authenticating to the Git SSH server")
createSourceGitCmd.Flags().BoolVar(&sourceGitArgs.recurseSubmodules, "recurse-submodules", false,
"when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces")
createSourceCmd.AddCommand(createSourceGitCmd) createSourceCmd.AddCommand(createSourceGitCmd)
} }
@@ -146,16 +149,6 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("url is required") return fmt.Errorf("url is required")
} }
if sourceGitArgs.gitImplementation.String() != sourcev1.LibGit2Implementation && sourceGitArgs.caFile != "" {
return fmt.Errorf("specifing a CA file requires --git-implementation=%s", sourcev1.LibGit2Implementation)
}
tmpDir, err := ioutil.TempDir("", name)
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
u, err := url.Parse(sourceGitArgs.url) u, err := url.Parse(sourceGitArgs.url)
if err != nil { if err != nil {
return fmt.Errorf("git URL parse failed: %w", err) return fmt.Errorf("git URL parse failed: %w", err)
@@ -164,6 +157,20 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme) return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
} }
if sourceGitArgs.caFile != "" && u.Scheme == "ssh" {
return fmt.Errorf("specifing a CA file is not supported for Git over SSH")
}
if sourceGitArgs.recurseSubmodules && sourceGitArgs.gitImplementation == sourcev1.LibGit2Implementation {
return fmt.Errorf("recurse submodules requires --git-implementation=%s", sourcev1.GoGitImplementation)
}
tmpDir, err := ioutil.TempDir("", name)
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
sourceLabels, err := parseLabels() sourceLabels, err := parseLabels()
if err != nil { if err != nil {
return err return err
@@ -180,7 +187,8 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
Interval: metav1.Duration{ Interval: metav1.Duration{
Duration: createArgs.interval, Duration: createArgs.interval,
}, },
Reference: &sourcev1.GitRepositoryRef{}, RecurseSubmodules: sourceGitArgs.recurseSubmodules,
Reference: &sourcev1.GitRepositoryRef{},
}, },
} }
@@ -232,6 +240,10 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
secretOpts.Username = sourceGitArgs.username secretOpts.Username = sourceGitArgs.username
secretOpts.Password = sourceGitArgs.password secretOpts.Password = sourceGitArgs.password
secretOpts.CAFilePath = sourceGitArgs.caFile secretOpts.CAFilePath = sourceGitArgs.caFile
case "http":
logger.Warningf("insecure configuration: credentials configured for an HTTP URL")
secretOpts.Username = sourceGitArgs.username
secretOpts.Password = sourceGitArgs.password
} }
secret, err := sourcesecret.Generate(secretOpts) secret, err := sourcesecret.Generate(secretOpts)
if err != nil { if err != nil {

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var deleteAlertCmd = &cobra.Command{ var deleteAlertCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var deleteAlertProviderCmd = &cobra.Command{ var deleteAlertProviderCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
) )
var deleteHelmReleaseCmd = &cobra.Command{ var deleteHelmReleaseCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
) )
var deleteKsCmd = &cobra.Command{ var deleteKsCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var deleteReceiverCmd = &cobra.Command{ var deleteReceiverCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var deleteSourceBucketCmd = &cobra.Command{ var deleteSourceBucketCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var deleteSourceGitCmd = &cobra.Command{ var deleteSourceGitCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var deleteSourceHelmCmd = &cobra.Command{ var deleteSourceHelmCmd = &cobra.Command{

View File

@@ -17,9 +17,10 @@ limitations under the License.
package main package main
import ( import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var exportAlertCmd = &cobra.Command{ var exportAlertCmd = &cobra.Command{

View File

@@ -17,9 +17,10 @@ limitations under the License.
package main package main
import ( import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var exportAlertProviderCmd = &cobra.Command{ var exportAlertProviderCmd = &cobra.Command{

View File

@@ -17,9 +17,10 @@ limitations under the License.
package main package main
import ( import (
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
) )
var exportHelmReleaseCmd = &cobra.Command{ var exportHelmReleaseCmd = &cobra.Command{

View File

@@ -17,9 +17,10 @@ limitations under the License.
package main package main
import ( import (
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
) )
var exportKsCmd = &cobra.Command{ var exportKsCmd = &cobra.Command{

View File

@@ -17,9 +17,10 @@ limitations under the License.
package main package main
import ( import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var exportReceiverCmd = &cobra.Command{ var exportReceiverCmd = &cobra.Command{

View File

@@ -19,12 +19,13 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/fluxcd/flux2/internal/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils"
) )
// exportableWithSecret represents a type that you can fetch from the Kubernetes // exportableWithSecret represents a type that you can fetch from the Kubernetes

View File

@@ -17,10 +17,11 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var exportSourceBucketCmd = &cobra.Command{ var exportSourceBucketCmd = &cobra.Command{

View File

@@ -17,10 +17,11 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var exportSourceGitCmd = &cobra.Command{ var exportSourceGitCmd = &cobra.Command{

View File

@@ -17,10 +17,11 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var exportSourceHelmCmd = &cobra.Command{ var exportSourceHelmCmd = &cobra.Command{

View File

@@ -20,8 +20,9 @@ import (
"strconv" "strconv"
"strings" "strings"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var getAlertCmd = &cobra.Command{ var getAlertCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var getAlertProviderCmd = &cobra.Command{ var getAlertProviderCmd = &cobra.Command{

View File

@@ -17,9 +17,10 @@ limitations under the License.
package main package main
import ( import (
"github.com/spf13/cobra"
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1" autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
"github.com/spf13/cobra"
) )
var getImageAllCmd = &cobra.Command{ var getImageAllCmd = &cobra.Command{

View File

@@ -20,8 +20,9 @@ import (
"strconv" "strconv"
"strings" "strings"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
) )
var getKsCmd = &cobra.Command{ var getKsCmd = &cobra.Command{

View File

@@ -20,8 +20,9 @@ import (
"strconv" "strconv"
"strings" "strings"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
var getReceiverCmd = &cobra.Command{ var getReceiverCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var getSourceAllCmd = &cobra.Command{ var getSourceAllCmd = &cobra.Command{

View File

@@ -17,11 +17,12 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"strconv" "strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var getSourceBucketCmd = &cobra.Command{ var getSourceBucketCmd = &cobra.Command{

View File

@@ -20,8 +20,9 @@ import (
"strconv" "strconv"
"strings" "strings"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var getSourceHelmChartCmd = &cobra.Command{ var getSourceHelmChartCmd = &cobra.Command{

View File

@@ -20,8 +20,9 @@ import (
"strconv" "strconv"
"strings" "strings"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var getSourceGitCmd = &cobra.Command{ var getSourceGitCmd = &cobra.Command{

View File

@@ -20,8 +20,9 @@ import (
"strconv" "strconv"
"strings" "strings"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var getSourceHelmCmd = &cobra.Command{ var getSourceHelmCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
) )
// helmv2.HelmRelease // helmv2.HelmRelease

View File

@@ -176,15 +176,15 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("install failed: %w", err) return fmt.Errorf("install failed: %w", err)
} }
if rootArgs.verbose { if installArgs.export {
fmt.Print(manifest.Content)
} else if installArgs.export {
fmt.Println("---") fmt.Println("---")
fmt.Println("# Flux version:", installArgs.version) fmt.Println("# Flux version:", installArgs.version)
fmt.Println("# Components:", strings.Join(components, ",")) fmt.Println("# Components:", strings.Join(components, ","))
fmt.Print(manifest.Content) fmt.Print(manifest.Content)
fmt.Println("---") fmt.Println("---")
return nil return nil
} else if rootArgs.verbose {
fmt.Print(manifest.Content)
} }
logger.Successf("manifests build completed") logger.Successf("manifests build completed")

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
) )
// kustomizev1.Kustomization // kustomizev1.Kustomization

View File

@@ -41,6 +41,10 @@ func (l stderrLogger) Successf(format string, a ...interface{}) {
fmt.Fprintln(l.stderr, ``, fmt.Sprintf(format, a...)) fmt.Fprintln(l.stderr, ``, fmt.Sprintf(format, a...))
} }
func (l stderrLogger) Warningf(format string, a ...interface{}) {
fmt.Fprintln(l.stderr, `⚠️`, fmt.Sprintf(format, a...))
}
func (l stderrLogger) Failuref(format string, a ...interface{}) { func (l stderrLogger) Failuref(format string, a ...interface{}) {
fmt.Fprintln(l.stderr, ``, fmt.Sprintf(format, a...)) fmt.Fprintln(l.stderr, ``, fmt.Sprintf(format, a...))
} }

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
// notificationv1.Receiver // notificationv1.Receiver

View File

@@ -21,7 +21,6 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra" "github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta" apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -30,6 +29,9 @@ import (
"k8s.io/client-go/util/retry" "k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
) )
@@ -97,12 +99,23 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
} }
logger.Successf("%s annotated", reconcile.kind) logger.Successf("%s annotated", reconcile.kind)
if reconcile.kind == v1beta1.AlertKind || reconcile.kind == v1beta1.ReceiverKind {
if err = wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReconcileReady(ctx, kubeClient, namespacedName, reconcile.object)); err != nil {
return err
}
logger.Successf(reconcile.object.successMessage())
return nil
}
lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest() lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()
logger.Waitingf("waiting for %s reconciliation", reconcile.kind) logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil { reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
return err return err
} }
logger.Successf("%s reconciliation completed", reconcile.kind) logger.Successf("%s reconciliation completed", reconcile.kind)
if apimeta.IsStatusConditionFalse(*reconcile.object.GetStatusConditions(), meta.ReadyCondition) { if apimeta.IsStatusConditionFalse(*reconcile.object.GetStatusConditions(), meta.ReadyCondition) {
@@ -140,3 +153,23 @@ func requestReconciliation(ctx context.Context, kubeClient client.Client,
return kubeClient.Update(ctx, obj.asClientObject()) return kubeClient.Update(ctx, obj.asClientObject())
}) })
} }
func isReconcileReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, obj reconcilable) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, obj.asClientObject())
if err != nil {
return false, err
}
if c := apimeta.FindStatusCondition(*obj.GetStatusConditions(), meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}

View File

@@ -17,16 +17,7 @@ limitations under the License.
package main package main
import ( import (
"context"
"fmt"
"time"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
@@ -37,61 +28,16 @@ var reconcileAlertCmd = &cobra.Command{
Long: `The reconcile alert command triggers a reconciliation of an Alert resource and waits for it to finish.`, Long: `The reconcile alert command triggers a reconciliation of an Alert resource and waits for it to finish.`,
Example: ` # Trigger a reconciliation for an existing alert Example: ` # Trigger a reconciliation for an existing alert
flux reconcile alert main`, flux reconcile alert main`,
RunE: reconcileAlertCmdRun, RunE: reconcileCommand{
apiType: alertType,
object: alertAdapter{&notificationv1.Alert{}},
}.run,
} }
func init() { func init() {
reconcileCmd.AddCommand(reconcileAlertCmd) reconcileCmd.AddCommand(reconcileAlertCmd)
} }
func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error { func (obj alertAdapter) lastHandledReconcileRequest() string {
if len(args) < 1 { return ""
return fmt.Errorf("Alert name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
if alert.Spec.Suspend {
return fmt.Errorf("resource is suspended")
}
logger.Actionf("annotating Alert %s in %s namespace", name, rootArgs.namespace)
if alert.Annotations == nil {
alert.Annotations = map[string]string{
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
alert.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
}
if err := kubeClient.Update(ctx, &alert); err != nil {
return err
}
logger.Successf("Alert annotated")
logger.Waitingf("waiting for reconciliation")
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err
}
logger.Successf("Alert reconciliation completed")
return nil
} }

View File

@@ -21,14 +21,14 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/utils"
) )
var reconcileAlertProviderCmd = &cobra.Command{ var reconcileAlertProviderCmd = &cobra.Command{

View File

@@ -17,20 +17,7 @@ limitations under the License.
package main package main
import ( import (
"context"
"fmt"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
@@ -47,7 +34,10 @@ The reconcile kustomization command triggers a reconciliation of a HelmRelease r
# Trigger a reconciliation of the HelmRelease's source and apply changes # Trigger a reconciliation of the HelmRelease's source and apply changes
flux reconcile hr podinfo --with-source`, flux reconcile hr podinfo --with-source`,
RunE: reconcileHrCmdRun, RunE: reconcileWithSourceCommand{
apiType: helmReleaseType,
object: helmReleaseAdapter{&helmv2.HelmRelease{}},
}.run,
} }
type reconcileHelmReleaseFlags struct { type reconcileHelmReleaseFlags struct {
@@ -62,117 +52,33 @@ func init() {
reconcileCmd.AddCommand(reconcileHrCmd) reconcileCmd.AddCommand(reconcileHrCmd)
} }
func reconcileHrCmdRun(cmd *cobra.Command, args []string) error { func (obj helmReleaseAdapter) lastHandledReconcileRequest() string {
if len(args) < 1 { return obj.Status.GetLastHandledReconcileRequest()
return fmt.Errorf("HelmRelease name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
var helmRelease helmv2.HelmRelease
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
if err != nil {
return err
}
if helmRelease.Spec.Suspend {
return fmt.Errorf("resource is suspended")
}
if rhrArgs.syncHrWithSource {
nsCopy := rootArgs.namespace
if helmRelease.Spec.Chart.Spec.SourceRef.Namespace != "" {
rootArgs.namespace = helmRelease.Spec.Chart.Spec.SourceRef.Namespace
}
switch helmRelease.Spec.Chart.Spec.SourceRef.Kind {
case sourcev1.HelmRepositoryKind:
err = reconcileCommand{
apiType: helmRepositoryType,
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
}.run(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
case sourcev1.GitRepositoryKind:
err = reconcileCommand{
apiType: gitRepositoryType,
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
}.run(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
case sourcev1.BucketKind:
err = reconcileCommand{
apiType: bucketType,
object: bucketAdapter{&sourcev1.Bucket{}},
}.run(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
}
if err != nil {
return err
}
rootArgs.namespace = nsCopy
}
lastHandledReconcileAt := helmRelease.Status.LastHandledReconcileAt
logger.Actionf("annotating HelmRelease %s in %s namespace", name, rootArgs.namespace)
if err := requestHelmReleaseReconciliation(ctx, kubeClient, namespacedName, &helmRelease); err != nil {
return err
}
logger.Successf("HelmRelease annotated")
logger.Waitingf("waiting for HelmRelease reconciliation")
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
helmReleaseReconciliationHandled(ctx, kubeClient, namespacedName, &helmRelease, lastHandledReconcileAt),
); err != nil {
return err
}
logger.Successf("HelmRelease reconciliation completed")
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
if err != nil {
return err
}
if c := apimeta.FindStatusCondition(helmRelease.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionFalse:
return fmt.Errorf("HelmRelease reconciliation failed: %s", c.Message)
default:
logger.Successf("reconciled revision %s", helmRelease.Status.LastAppliedRevision)
}
}
return nil
} }
func helmReleaseReconciliationHandled(ctx context.Context, kubeClient client.Client, func (obj helmReleaseAdapter) reconcileSource() bool {
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease, lastHandledReconcileAt string) wait.ConditionFunc { return rhrArgs.syncHrWithSource
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, helmRelease)
if err != nil {
return false, err
}
return helmRelease.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
}
} }
func requestHelmReleaseReconciliation(ctx context.Context, kubeClient client.Client, func (obj helmReleaseAdapter) getSource() (reconcileCommand, string) {
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) error { var cmd reconcileCommand
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { switch obj.Spec.Chart.Spec.SourceRef.Kind {
if err := kubeClient.Get(ctx, namespacedName, helmRelease); err != nil { case sourcev1.HelmRepositoryKind:
return err cmd = reconcileCommand{
apiType: helmRepositoryType,
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
} }
if helmRelease.Annotations == nil { case sourcev1.GitRepositoryKind:
helmRelease.Annotations = map[string]string{ cmd = reconcileCommand{
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), apiType: gitRepositoryType,
} object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
} else {
helmRelease.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
} }
return kubeClient.Update(ctx, helmRelease) case sourcev1.BucketKind:
}) cmd = reconcileCommand{
apiType: bucketType,
object: bucketAdapter{&sourcev1.Bucket{}},
}
}
return cmd, obj.Spec.Chart.Spec.SourceRef.Name
} }

View File

@@ -17,19 +17,7 @@ limitations under the License.
package main package main
import ( import (
"context"
"fmt"
"time"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
@@ -46,7 +34,10 @@ The reconcile kustomization command triggers a reconciliation of a Kustomization
# Trigger a sync of the Kustomization's source and apply changes # Trigger a sync of the Kustomization's source and apply changes
flux reconcile kustomization podinfo --with-source`, flux reconcile kustomization podinfo --with-source`,
RunE: reconcileKsCmdRun, RunE: reconcileWithSourceCommand{
apiType: kustomizationType,
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
}.run,
} }
type reconcileKsFlags struct { type reconcileKsFlags struct {
@@ -61,104 +52,28 @@ func init() {
reconcileCmd.AddCommand(reconcileKsCmd) reconcileCmd.AddCommand(reconcileKsCmd)
} }
func reconcileKsCmdRun(cmd *cobra.Command, args []string) error { func (obj kustomizationAdapter) lastHandledReconcileRequest() string {
if len(args) < 1 { return obj.Status.GetLastHandledReconcileRequest()
return fmt.Errorf("Kustomization name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
var kustomization kustomizev1.Kustomization
err = kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil {
return err
}
if kustomization.Spec.Suspend {
return fmt.Errorf("resource is suspended")
}
if rksArgs.syncKsWithSource {
nsCopy := rootArgs.namespace
if kustomization.Spec.SourceRef.Namespace != "" {
rootArgs.namespace = kustomization.Spec.SourceRef.Namespace
}
switch kustomization.Spec.SourceRef.Kind {
case sourcev1.GitRepositoryKind:
err = reconcileCommand{
apiType: gitRepositoryType,
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
}.run(nil, []string{kustomization.Spec.SourceRef.Name})
case sourcev1.BucketKind:
err = reconcileCommand{
apiType: bucketType,
object: bucketAdapter{&sourcev1.Bucket{}},
}.run(nil, []string{kustomization.Spec.SourceRef.Name})
}
if err != nil {
return err
}
rootArgs.namespace = nsCopy
}
lastHandledReconcileAt := kustomization.Status.LastHandledReconcileAt
logger.Actionf("annotating Kustomization %s in %s namespace", name, rootArgs.namespace)
if err := requestKustomizeReconciliation(ctx, kubeClient, namespacedName, &kustomization); err != nil {
return err
}
logger.Successf("Kustomization annotated")
logger.Waitingf("waiting for Kustomization reconciliation")
if err := wait.PollImmediate(
rootArgs.pollInterval, rootArgs.timeout,
kustomizeReconciliationHandled(ctx, kubeClient, namespacedName, &kustomization, lastHandledReconcileAt),
); err != nil {
return err
}
logger.Successf("Kustomization reconciliation completed")
if apimeta.IsStatusConditionFalse(kustomization.Status.Conditions, meta.ReadyCondition) {
return fmt.Errorf("Kustomization reconciliation failed")
}
logger.Successf("reconciled revision %s", kustomization.Status.LastAppliedRevision)
return nil
} }
func kustomizeReconciliationHandled(ctx context.Context, kubeClient client.Client, func (obj kustomizationAdapter) reconcileSource() bool {
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization, lastHandledReconcileAt string) wait.ConditionFunc { return rksArgs.syncKsWithSource
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, kustomization)
if err != nil {
return false, err
}
return kustomization.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
}
} }
func requestKustomizeReconciliation(ctx context.Context, kubeClient client.Client, func (obj kustomizationAdapter) getSource() (reconcileCommand, string) {
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) error { var cmd reconcileCommand
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { switch obj.Spec.SourceRef.Kind {
if err := kubeClient.Get(ctx, namespacedName, kustomization); err != nil { case sourcev1.GitRepositoryKind:
return err cmd = reconcileCommand{
apiType: gitRepositoryType,
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
} }
if kustomization.Annotations == nil { case sourcev1.BucketKind:
kustomization.Annotations = map[string]string{ cmd = reconcileCommand{
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), apiType: bucketType,
} object: bucketAdapter{&sourcev1.Bucket{}},
} else {
kustomization.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
} }
return kubeClient.Update(ctx, kustomization) }
})
return cmd, obj.Spec.SourceRef.Name
} }

View File

@@ -21,14 +21,14 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/utils"
) )
var reconcileReceiverCmd = &cobra.Command{ var reconcileReceiverCmd = &cobra.Command{

View File

@@ -19,7 +19,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra" "github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta" apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )

View File

@@ -18,8 +18,10 @@ package main
import ( import (
"fmt" "fmt"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var reconcileSourceGitCmd = &cobra.Command{ var reconcileSourceGitCmd = &cobra.Command{

View File

@@ -18,8 +18,10 @@ package main
import ( import (
"fmt" "fmt"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var reconcileSourceHelmCmd = &cobra.Command{ var reconcileSourceHelmCmd = &cobra.Command{

View File

@@ -0,0 +1,91 @@
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/utils"
)
type reconcileWithSource interface {
adapter
reconcilable
reconcileSource() bool
getSource() (reconcileCommand, string)
}
type reconcileWithSourceCommand struct {
apiType
object reconcileWithSource
}
func (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("%s name is required", reconcile.kind)
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, reconcile.object.asClientObject())
if err != nil {
return err
}
if reconcile.object.isSuspended() {
return fmt.Errorf("resource is suspended")
}
if reconcile.object.reconcileSource() {
nsCopy := rootArgs.namespace
objectNs := reconcile.object.asClientObject().GetNamespace()
if objectNs != "" {
rootArgs.namespace = reconcile.object.asClientObject().GetNamespace()
}
reconcileCmd, sourceName := reconcile.object.getSource()
err := reconcileCmd.run(nil, []string{sourceName})
if err != nil {
return err
}
rootArgs.namespace = nsCopy
}
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
}
logger.Successf("%s annotated", reconcile.kind)
lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
return err
}
logger.Successf("%s reconciliation completed", reconcile.kind)
if apimeta.IsStatusConditionFalse(*reconcile.object.GetStatusConditions(), meta.ReadyCondition) {
return fmt.Errorf("%s reconciliation failed", reconcile.kind)
}
logger.Successf(reconcile.object.successMessage())
return nil
}

View File

@@ -17,18 +17,7 @@ limitations under the License.
package main package main
import ( import (
"context"
"fmt"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra" "github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
@@ -40,72 +29,24 @@ var resumeAlertCmd = &cobra.Command{
finish the apply.`, finish the apply.`,
Example: ` # Resume reconciliation for an existing Alert Example: ` # Resume reconciliation for an existing Alert
flux resume alert main`, flux resume alert main`,
RunE: resumeAlertCmdRun, RunE: resumeCommand{
apiType: alertType,
object: alertAdapter{&notificationv1.Alert{}},
}.run,
} }
func init() { func init() {
resumeCmd.AddCommand(resumeAlertCmd) resumeCmd.AddCommand(resumeAlertCmd)
} }
func resumeAlertCmdRun(cmd *cobra.Command, args []string) error { func (obj alertAdapter) getObservedGeneration() int64 {
if len(args) < 1 { return obj.Alert.Status.ObservedGeneration
return fmt.Errorf("Alert name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
logger.Actionf("resuming Alert %s in %s namespace", name, rootArgs.namespace)
alert.Spec.Suspend = false
if err := kubeClient.Update(ctx, &alert); err != nil {
return err
}
logger.Successf("Alert resumed")
logger.Waitingf("waiting for Alert reconciliation")
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isAlertResumed(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err
}
logger.Successf("Alert reconciliation completed")
return nil
} }
func isAlertResumed(ctx context.Context, kubeClient client.Client, func (obj alertAdapter) setUnsuspended() {
namespacedName types.NamespacedName, alert *notificationv1.Alert) wait.ConditionFunc { obj.Alert.Spec.Suspend = false
return func() (bool, error) { }
err := kubeClient.Get(ctx, namespacedName, alert)
if err != nil { func (obj alertAdapter) successMessage() string {
return false, err return "Alert reconciliation completed"
}
if c := apimeta.FindStatusCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
if c.Reason == meta.SuspendedReason {
return false, nil
}
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
} }

View File

@@ -18,8 +18,10 @@ package main
import ( import (
"fmt" "fmt"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
) )
var resumeHrCmd = &cobra.Command{ var resumeHrCmd = &cobra.Command{

View File

@@ -19,8 +19,9 @@ package main
import ( import (
"fmt" "fmt"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
) )
var resumeKsCmd = &cobra.Command{ var resumeKsCmd = &cobra.Command{

View File

@@ -17,18 +17,7 @@ limitations under the License.
package main package main
import ( import (
"context"
"fmt"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra" "github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
@@ -40,73 +29,24 @@ var resumeReceiverCmd = &cobra.Command{
finish the apply.`, finish the apply.`,
Example: ` # Resume reconciliation for an existing Receiver Example: ` # Resume reconciliation for an existing Receiver
flux resume receiver main`, flux resume receiver main`,
RunE: resumeReceiverCmdRun, RunE: resumeCommand{
apiType: receiverType,
object: receiverAdapter{&notificationv1.Receiver{}},
}.run,
} }
func init() { func init() {
resumeCmd.AddCommand(resumeReceiverCmd) resumeCmd.AddCommand(resumeReceiverCmd)
} }
func resumeReceiverCmdRun(cmd *cobra.Command, args []string) error { func (obj receiverAdapter) getObservedGeneration() int64 {
if len(args) < 1 { return obj.Receiver.Status.ObservedGeneration
return fmt.Errorf("Receiver name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
logger.Actionf("resuming Receiver %s in %s namespace", name, rootArgs.namespace)
receiver.Spec.Suspend = false
if err := kubeClient.Update(ctx, &receiver); err != nil {
return err
}
logger.Successf("Receiver resumed")
logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReceiverResumed(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err
}
logger.Successf("Receiver reconciliation completed")
return nil
} }
func isReceiverResumed(ctx context.Context, kubeClient client.Client, func (obj receiverAdapter) setUnsuspended() {
namespacedName types.NamespacedName, receiver *notificationv1.Receiver) wait.ConditionFunc { obj.Receiver.Spec.Suspend = false
return func() (bool, error) { }
err := kubeClient.Get(ctx, namespacedName, receiver)
if err != nil { func (obj receiverAdapter) successMessage() string {
return false, err return "Receiver reconciliation completed"
}
if c := apimeta.FindStatusCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
if c.Reason == meta.SuspendedReason {
return false, nil
}
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
} }

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var resumeSourceBucketCmd = &cobra.Command{ var resumeSourceBucketCmd = &cobra.Command{

View File

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

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var resumeSourceGitCmd = &cobra.Command{ var resumeSourceGitCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var resumeSourceHelmCmd = &cobra.Command{ var resumeSourceHelmCmd = &cobra.Command{

View File

@@ -17,13 +17,8 @@ limitations under the License.
package main package main
import ( import (
"context"
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"github.com/fluxcd/flux2/internal/utils"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
@@ -33,43 +28,20 @@ var suspendAlertCmd = &cobra.Command{
Long: "The suspend command disables the reconciliation of a Alert resource.", Long: "The suspend command disables the reconciliation of a Alert resource.",
Example: ` # Suspend reconciliation for an existing Alert Example: ` # Suspend reconciliation for an existing Alert
flux suspend alert main`, flux suspend alert main`,
RunE: suspendAlertCmdRun, RunE: suspendCommand{
apiType: alertType,
object: &alertAdapter{&notificationv1.Alert{}},
}.run,
} }
func init() { func init() {
suspendCmd.AddCommand(suspendAlertCmd) suspendCmd.AddCommand(suspendAlertCmd)
} }
func suspendAlertCmdRun(cmd *cobra.Command, args []string) error { func (obj alertAdapter) isSuspended() bool {
if len(args) < 1 { return obj.Alert.Spec.Suspend
return fmt.Errorf("Alert name is required") }
}
name := args[0] func (obj alertAdapter) setSuspended() {
obj.Alert.Spec.Suspend = true
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
var alert notificationv1.Alert
err = kubeClient.Get(ctx, namespacedName, &alert)
if err != nil {
return err
}
logger.Actionf("suspending Alert %s in %s namespace", name, rootArgs.namespace)
alert.Spec.Suspend = true
if err := kubeClient.Update(ctx, &alert); err != nil {
return err
}
logger.Successf("Alert suspended")
return nil
} }

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
) )
var suspendHrCmd = &cobra.Command{ var suspendHrCmd = &cobra.Command{

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
) )
var suspendKsCmd = &cobra.Command{ var suspendKsCmd = &cobra.Command{

View File

@@ -17,13 +17,8 @@ limitations under the License.
package main package main
import ( import (
"context"
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"github.com/fluxcd/flux2/internal/utils"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
) )
@@ -33,43 +28,20 @@ var suspendReceiverCmd = &cobra.Command{
Long: "The suspend command disables the reconciliation of a Receiver resource.", Long: "The suspend command disables the reconciliation of a Receiver resource.",
Example: ` # Suspend reconciliation for an existing Receiver Example: ` # Suspend reconciliation for an existing Receiver
flux suspend receiver main`, flux suspend receiver main`,
RunE: suspendReceiverCmdRun, RunE: suspendCommand{
apiType: receiverType,
object: &receiverAdapter{&notificationv1.Receiver{}},
}.run,
} }
func init() { func init() {
suspendCmd.AddCommand(suspendReceiverCmd) suspendCmd.AddCommand(suspendReceiverCmd)
} }
func suspendReceiverCmdRun(cmd *cobra.Command, args []string) error { func (obj receiverAdapter) isSuspended() bool {
if len(args) < 1 { return obj.Receiver.Spec.Suspend
return fmt.Errorf("Receiver name is required") }
}
name := args[0] func (obj receiverAdapter) setSuspended() {
obj.Receiver.Spec.Suspend = true
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
var receiver notificationv1.Receiver
err = kubeClient.Get(ctx, namespacedName, &receiver)
if err != nil {
return err
}
logger.Actionf("suspending Receiver %s in %s namespace", name, rootArgs.namespace)
receiver.Spec.Suspend = true
if err := kubeClient.Update(ctx, &receiver); err != nil {
return err
}
logger.Successf("Receiver suspended")
return nil
} }

View File

@@ -17,8 +17,9 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var suspendSourceBucketCmd = &cobra.Command{ var suspendSourceBucketCmd = &cobra.Command{

View File

@@ -17,9 +17,9 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var suspendSourceHelmChartCmd = &cobra.Command{ var suspendSourceHelmChartCmd = &cobra.Command{

View File

@@ -17,9 +17,9 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var suspendSourceGitCmd = &cobra.Command{ var suspendSourceGitCmd = &cobra.Command{

View File

@@ -17,9 +17,9 @@ limitations under the License.
package main package main
import ( import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var suspendSourceHelmCmd = &cobra.Command{ var suspendSourceHelmCmd = &cobra.Command{

View File

@@ -12,19 +12,30 @@ The bootstrap sub-commands bootstrap the toolkit components on the targeted Git
### Options ### Options
``` ```
--branch string default branch (for GitHub this must match the default branch setting for the organization) (default "main") --author-email string author email for Git commits
--cluster-domain string internal cluster domain (default "cluster.local") --author-name string author name for Git commits (default "Flux")
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller]) --branch string Git branch (default "main")
--components-extra strings list of components in addition to those supplied or defaulted, accepts comma-separated values --ca-file string path to TLS CA file used for validating self-signed certificates
-h, --help help for bootstrap --cluster-domain string internal cluster domain (default "cluster.local")
--image-pull-secret string Kubernetes secret name used for pulling the toolkit images from a private registry --commit-message-appendix string string to add to the commit messages, e.g. '[ci skip]'
--log-level logLevel log level, available options are: (debug, info, error) (default info) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller])
--network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true) --components-extra strings list of components in addition to those supplied or defaulted, accepts comma-separated values
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd") -h, --help help for bootstrap
--token-auth when enabled, the personal access token will be used instead of SSH deploy key --image-pull-secret string Kubernetes secret name used for pulling the toolkit images from a private registry
--toleration-keys strings list of toleration keys used to schedule the components pods onto nodes with matching taints --log-level logLevel log level, available options are: (debug, info, error) (default info)
-v, --version string toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases --network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true) --private-key-file string path to a private key file used for authenticating to the Git SSH server
--recurse-submodules when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd")
--secret-name string name of the secret the sync credentials can be found in or stored to (default "flux-system")
--ssh-ecdsa-curve ecdsaCurve SSH ECDSA public key curve (p256, p384, p521) (default p384)
--ssh-hostname string SSH hostname, to be used when the SSH host differs from the HTTPS one
--ssh-key-algorithm publicKeyAlgorithm SSH public key algorithm (rsa, ecdsa, ed25519) (default rsa)
--ssh-rsa-bits rsaKeyBits SSH RSA public key bit size (multiplies of 8) (default 2048)
--token-auth when enabled, the personal access token will be used instead of SSH deploy key
--toleration-keys strings list of toleration keys used to schedule the components pods onto nodes with matching taints
-v, --version string toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
``` ```
### Options inherited from parent commands ### Options inherited from parent commands
@@ -40,6 +51,7 @@ The bootstrap sub-commands bootstrap the toolkit components on the targeted Git
### SEE ALSO ### SEE ALSO
* [flux](../flux/) - Command line utility for assembling Kubernetes CD pipelines * [flux](../flux/) - Command line utility for assembling Kubernetes CD pipelines
* [flux bootstrap git](../flux_bootstrap_git/) - Bootstrap toolkit components in a Git repository
* [flux bootstrap github](../flux_bootstrap_github/) - Bootstrap toolkit components in a GitHub repository * [flux bootstrap github](../flux_bootstrap_github/) - Bootstrap toolkit components in a GitHub repository
* [flux bootstrap gitlab](../flux_bootstrap_gitlab/) - Bootstrap toolkit components in a GitLab repository * [flux bootstrap gitlab](../flux_bootstrap_gitlab/) - Bootstrap toolkit components in a GitLab repository

View File

@@ -0,0 +1,80 @@
---
title: "flux bootstrap git command"
---
## flux bootstrap git
Bootstrap toolkit components in a Git repository
### Synopsis
The bootstrap git command commits the toolkit components manifests to the
branch of a Git repository. It then configures the target cluster to synchronize with
the repository. If the toolkit components are present on the cluster, the bootstrap
command will perform an upgrade if needed.
```
flux bootstrap git [flags]
```
### Examples
```
# Run bootstrap for a Git repository and authenticate with your SSH agent
flux bootstrap git --url=ssh://git@example.com/repository.git
# Run bootstrap for a Git repository and authenticate using a password
flux bootstrap git --url=https://example.com/repository.git --password=<password>
# Run bootstrap for a Git repository with a passwordless private key
flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key>
```
### Options
```
-h, --help help for git
--interval duration sync interval (default 1m0s)
-p, --password string basic authentication password
--path safeRelativePath path relative to the repository root, when specified the cluster sync will be scoped to this path
--url string Git repository URL
-u, --username string basic authentication username (default "git")
```
### Options inherited from parent commands
```
--author-email string author email for Git commits
--author-name string author name for Git commits (default "Flux")
--branch string Git branch (default "main")
--ca-file string path to TLS CA file used for validating self-signed certificates
--cluster-domain string internal cluster domain (default "cluster.local")
--commit-message-appendix string string to add to the commit messages, e.g. '[ci skip]'
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller])
--components-extra strings list of components in addition to those supplied or defaulted, accepts comma-separated values
--context string kubernetes context to use
--image-pull-secret string Kubernetes secret name used for pulling the toolkit images from a private registry
--kubeconfig string absolute path to the kubeconfig file
--log-level logLevel log level, available options are: (debug, info, error) (default info)
-n, --namespace string the namespace scope for this operation (default "flux-system")
--network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
--private-key-file string path to a private key file used for authenticating to the Git SSH server
--recurse-submodules when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd")
--secret-name string name of the secret the sync credentials can be found in or stored to (default "flux-system")
--ssh-ecdsa-curve ecdsaCurve SSH ECDSA public key curve (p256, p384, p521) (default p384)
--ssh-hostname string SSH hostname, to be used when the SSH host differs from the HTTPS one
--ssh-key-algorithm publicKeyAlgorithm SSH public key algorithm (rsa, ecdsa, ed25519) (default rsa)
--ssh-rsa-bits rsaKeyBits SSH RSA public key bit size (multiplies of 8) (default 2048)
--timeout duration timeout for this operation (default 5m0s)
--token-auth when enabled, the personal access token will be used instead of SSH deploy key
--toleration-keys strings list of toleration keys used to schedule the components pods onto nodes with matching taints
--verbose print generated objects
-v, --version string toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
```
### SEE ALSO
* [flux bootstrap](../flux_bootstrap/) - Bootstrap toolkit components

View File

@@ -55,31 +55,42 @@ flux bootstrap github [flags]
--path safeRelativePath path relative to the repository root, when specified the cluster sync will be scoped to this path --path safeRelativePath path relative to the repository root, when specified the cluster sync will be scoped to this path
--personal if true, the owner is assumed to be a GitHub user; otherwise an org --personal if true, the owner is assumed to be a GitHub user; otherwise an org
--private if true, the repository is assumed to be private (default true) --private if true, the repository is assumed to be private (default true)
--read-write-key if true, the deploy key is configured with read/write permissions
--repository string GitHub repository name --repository string GitHub repository name
--ssh-hostname string GitHub SSH hostname, to be used when the SSH host differs from the HTTPS one
--team stringArray GitHub team to be given maintainer access --team stringArray GitHub team to be given maintainer access
``` ```
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--branch string default branch (for GitHub this must match the default branch setting for the organization) (default "main") --author-email string author email for Git commits
--cluster-domain string internal cluster domain (default "cluster.local") --author-name string author name for Git commits (default "Flux")
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller]) --branch string Git branch (default "main")
--components-extra strings list of components in addition to those supplied or defaulted, accepts comma-separated values --ca-file string path to TLS CA file used for validating self-signed certificates
--context string kubernetes context to use --cluster-domain string internal cluster domain (default "cluster.local")
--image-pull-secret string Kubernetes secret name used for pulling the toolkit images from a private registry --commit-message-appendix string string to add to the commit messages, e.g. '[ci skip]'
--kubeconfig string absolute path to the kubeconfig file --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller])
--log-level logLevel log level, available options are: (debug, info, error) (default info) --components-extra strings list of components in addition to those supplied or defaulted, accepts comma-separated values
-n, --namespace string the namespace scope for this operation (default "flux-system") --context string kubernetes context to use
--network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true) --image-pull-secret string Kubernetes secret name used for pulling the toolkit images from a private registry
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd") --kubeconfig string absolute path to the kubeconfig file
--timeout duration timeout for this operation (default 5m0s) --log-level logLevel log level, available options are: (debug, info, error) (default info)
--token-auth when enabled, the personal access token will be used instead of SSH deploy key -n, --namespace string the namespace scope for this operation (default "flux-system")
--toleration-keys strings list of toleration keys used to schedule the components pods onto nodes with matching taints --network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
--verbose print generated objects --private-key-file string path to a private key file used for authenticating to the Git SSH server
-v, --version string toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases --recurse-submodules when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true) --registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd")
--secret-name string name of the secret the sync credentials can be found in or stored to (default "flux-system")
--ssh-ecdsa-curve ecdsaCurve SSH ECDSA public key curve (p256, p384, p521) (default p384)
--ssh-hostname string SSH hostname, to be used when the SSH host differs from the HTTPS one
--ssh-key-algorithm publicKeyAlgorithm SSH public key algorithm (rsa, ecdsa, ed25519) (default rsa)
--ssh-rsa-bits rsaKeyBits SSH RSA public key bit size (multiplies of 8) (default 2048)
--timeout duration timeout for this operation (default 5m0s)
--token-auth when enabled, the personal access token will be used instead of SSH deploy key
--toleration-keys strings list of toleration keys used to schedule the components pods onto nodes with matching taints
--verbose print generated objects
-v, --version string toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
``` ```
### SEE ALSO ### SEE ALSO

View File

@@ -52,30 +52,42 @@ flux bootstrap gitlab [flags]
--path safeRelativePath path relative to the repository root, when specified the cluster sync will be scoped to this path --path safeRelativePath path relative to the repository root, when specified the cluster sync will be scoped to this path
--personal if true, the owner is assumed to be a GitLab user; otherwise a group --personal if true, the owner is assumed to be a GitLab user; otherwise a group
--private if true, the repository is assumed to be private (default true) --private if true, the repository is assumed to be private (default true)
--read-write-key if true, the deploy key is configured with read/write permissions
--repository string GitLab repository name --repository string GitLab repository name
--ssh-hostname string GitLab SSH hostname, to be used when the SSH host differs from the HTTPS one --team stringArray GitLab teams to be given maintainer access
``` ```
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--branch string default branch (for GitHub this must match the default branch setting for the organization) (default "main") --author-email string author email for Git commits
--cluster-domain string internal cluster domain (default "cluster.local") --author-name string author name for Git commits (default "Flux")
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller]) --branch string Git branch (default "main")
--components-extra strings list of components in addition to those supplied or defaulted, accepts comma-separated values --ca-file string path to TLS CA file used for validating self-signed certificates
--context string kubernetes context to use --cluster-domain string internal cluster domain (default "cluster.local")
--image-pull-secret string Kubernetes secret name used for pulling the toolkit images from a private registry --commit-message-appendix string string to add to the commit messages, e.g. '[ci skip]'
--kubeconfig string absolute path to the kubeconfig file --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller])
--log-level logLevel log level, available options are: (debug, info, error) (default info) --components-extra strings list of components in addition to those supplied or defaulted, accepts comma-separated values
-n, --namespace string the namespace scope for this operation (default "flux-system") --context string kubernetes context to use
--network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true) --image-pull-secret string Kubernetes secret name used for pulling the toolkit images from a private registry
--registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd") --kubeconfig string absolute path to the kubeconfig file
--timeout duration timeout for this operation (default 5m0s) --log-level logLevel log level, available options are: (debug, info, error) (default info)
--token-auth when enabled, the personal access token will be used instead of SSH deploy key -n, --namespace string the namespace scope for this operation (default "flux-system")
--toleration-keys strings list of toleration keys used to schedule the components pods onto nodes with matching taints --network-policy deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
--verbose print generated objects --private-key-file string path to a private key file used for authenticating to the Git SSH server
-v, --version string toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases --recurse-submodules when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true) --registry string container registry where the toolkit images are published (default "ghcr.io/fluxcd")
--secret-name string name of the secret the sync credentials can be found in or stored to (default "flux-system")
--ssh-ecdsa-curve ecdsaCurve SSH ECDSA public key curve (p256, p384, p521) (default p384)
--ssh-hostname string SSH hostname, to be used when the SSH host differs from the HTTPS one
--ssh-key-algorithm publicKeyAlgorithm SSH public key algorithm (rsa, ecdsa, ed25519) (default rsa)
--ssh-rsa-bits rsaKeyBits SSH RSA public key bit size (multiplies of 8) (default 2048)
--timeout duration timeout for this operation (default 5m0s)
--token-auth when enabled, the personal access token will be used instead of SSH deploy key
--toleration-keys strings list of toleration keys used to schedule the components pods onto nodes with matching taints
--verbose print generated objects
-v, --version string toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
--watch-all-namespaces watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
``` ```
### SEE ALSO ### SEE ALSO

View File

@@ -62,6 +62,12 @@ flux create helmrelease [name] [flags]
--source=HelmRepository/podinfo \ --source=HelmRepository/podinfo \
--chart=podinfo --chart=podinfo
# Create a HelmRelease using a source from a different namespace
flux create hr podinfo \
--namespace=default \
--source=HelmRepository/podinfo.flux-system \
--chart=podinfo
# Create a HelmRelease definition on disk without applying it on the cluster # Create a HelmRelease definition on disk without applying it on the cluster
flux create hr podinfo \ flux create hr podinfo \
--source=HelmRepository/podinfo \ --source=HelmRepository/podinfo \
@@ -79,7 +85,7 @@ flux create helmrelease [name] [flags]
-h, --help help for helmrelease -h, --help help for helmrelease
--release-name string name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>' --release-name string name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'
--service-account string the name of the service account to impersonate when reconciling this HelmRelease --service-account string the name of the service account to impersonate when reconciling this HelmRelease
--source helmChartSource source that contains the chart in the format '<kind>/<name>', where kind must be one of: (HelmRepository, GitRepository, Bucket) --source helmChartSource source that contains the chart in the format '<kind>/<name>.<namespace>', where kind must be one of: (HelmRepository, GitRepository, Bucket)
--target-namespace string namespace to install this release, defaults to the HelmRelease namespace --target-namespace string namespace to install this release, defaults to the HelmRelease namespace
--values stringArray local path to values.yaml files --values stringArray local path to values.yaml files
--values-from helmReleaseValuesFrom Kubernetes object reference that contains the values.yaml data key in the format '<kind>/<name>', where kind must be one of: (Secret, ConfigMap) --values-from helmReleaseValuesFrom Kubernetes object reference that contains the values.yaml data key in the format '<kind>/<name>', where kind must be one of: (Secret, ConfigMap)

View File

@@ -18,7 +18,7 @@ flux create kustomization [name] [flags]
``` ```
# Create a Kustomization resource from a source at a given path # Create a Kustomization resource from a source at a given path
flux create kustomization contour \ flux create kustomization contour \
--source=contour \ --source=GitRepository/contour \
--path="./examples/contour/" \ --path="./examples/contour/" \
--prune=true \ --prune=true \
--interval=10m \ --interval=10m \
@@ -30,7 +30,16 @@ flux create kustomization [name] [flags]
# Create a Kustomization resource that depends on the previous one # Create a Kustomization resource that depends on the previous one
flux create kustomization webapp \ flux create kustomization webapp \
--depends-on=contour \ --depends-on=contour \
--source=webapp \ --source=GitRepository/webapp \
--path="./deploy/overlays/dev" \
--prune=true \
--interval=5m \
--validation=client
# Create a Kustomization using a source from a different namespace
flux create kustomization podinfo \
--namespace=default \
--source=GitRepository/podinfo.flux-system \
--path="./deploy/overlays/dev" \ --path="./deploy/overlays/dev" \
--prune=true \ --prune=true \
--interval=5m \ --interval=5m \
@@ -55,7 +64,7 @@ flux create kustomization [name] [flags]
--path safeRelativePath path to the directory containing a kustomization.yaml file (default ./) --path safeRelativePath path to the directory containing a kustomization.yaml file (default ./)
--prune enable garbage collection --prune enable garbage collection
--service-account string the name of the service account to impersonate when reconciling this Kustomization --service-account string the name of the service account to impersonate when reconciling this Kustomization
--source kustomizationSource source that contains the Kubernetes manifests in the format '[<kind>/]<name>', where kind must be one of: (GitRepository, Bucket), if kind is not specified it defaults to GitRepository --source kustomizationSource source that contains the Kubernetes manifests in the format '[<kind>/]<name>.<namespace>', where kind must be one of: (GitRepository, Bucket), if kind is not specified it defaults to GitRepository
--target-namespace string overrides the namespace of all Kustomization objects reconciled by this Kustomization --target-namespace string overrides the namespace of all Kustomization objects reconciled by this Kustomization
--validation string validate the manifests before applying them on the cluster, can be 'client' or 'server' --validation string validate the manifests before applying them on the cluster, can be 'client' or 'server'
``` ```

View File

@@ -65,11 +65,12 @@ flux create source git [name] [flags]
``` ```
--branch string git branch (default "master") --branch string git branch (default "master")
--ca-file string path to TLS CA file used for validating self-signed certificates, requires libgit2 --ca-file string path to TLS CA file used for validating self-signed certificates
--git-implementation gitImplementation the Git implementation to use, available options are: (go-git, libgit2) --git-implementation gitImplementation the Git implementation to use, available options are: (go-git, libgit2)
-h, --help help for git -h, --help help for git
-p, --password string basic authentication password -p, --password string basic authentication password
--private-key-file string path to a passwordless private key file used for authenticating to the Git SSH server --private-key-file string path to a passwordless private key file used for authenticating to the Git SSH server
--recurse-submodules when enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces
--secret-ref string the name of an existing secret containing SSH or basic credentials --secret-ref string the name of an existing secret containing SSH or basic credentials
--ssh-ecdsa-curve ecdsaCurve SSH ECDSA public key curve (p256, p384, p521) (default p384) --ssh-ecdsa-curve ecdsaCurve SSH ECDSA public key curve (p256, p384, p521) (default p384)
--ssh-key-algorithm publicKeyAlgorithm SSH public key algorithm (rsa, ecdsa, ed25519) (default rsa) --ssh-key-algorithm publicKeyAlgorithm SSH public key algorithm (rsa, ecdsa, ed25519) (default rsa)

View File

@@ -459,7 +459,7 @@ $ git push
Now you can sync the new commit, and check that the object is working: Now you can sync the new commit, and check that the object is working:
```bash ```bash
$ flux reocncile kustomization --with-source flux-system $ flux reconcile kustomization --with-source flux-system
► annotating GitRepository flux-system in flux-system namespace ► annotating GitRepository flux-system in flux-system namespace
✔ GitRepository annotated ✔ GitRepository annotated
◎ waiting for GitRepository reconciliation ◎ waiting for GitRepository reconciliation
@@ -541,13 +541,13 @@ spec:
order: asc order: asc
``` ```
The `.spec.pattern` field gives a regular expression that a tag must match to be included. The The `.spec.filterTags.pattern` field gives a regular expression that a tag must match to be included. The
`.spec.extract` field gives a replacement pattern that can refer back to capture groups in the `.spec.filterTags.extract` field gives a replacement pattern that can refer back to capture groups in the
filter pattern. The extracted values are sorted to find the selected image tag. In this case, the filter pattern. The extracted values are sorted to find the selected image tag. In this case, the
timestamp part of the tag will be extracted and sorted numerically in ascending order. See [the timestamp part of the tag will be extracted and sorted numerically in ascending order. See [the
reference docs][imagepolicy-ref] for more examples. reference docs][imagepolicy-ref] for more examples.
Once you have made sure you have image tags and an `ImagePolicy` that works, jump ahead to [Checking Once you have made sure you have image tags and an `ImagePolicy`, jump ahead to [Checking
the ImagePolicy works](#checking-that-the-image-policy-works). the ImagePolicy works](#checking-that-the-image-policy-works).
### How to use SemVer image tags ### How to use SemVer image tags

View File

@@ -31,6 +31,11 @@ are also supported with their own sub-commands.
Binaries for macOS, Windows and Linux AMD64/ARM are available for download on the Binaries for macOS, Windows and Linux AMD64/ARM are available for download on the
[release page](https://github.com/fluxcd/flux2/releases). [release page](https://github.com/fluxcd/flux2/releases).
A container image with `kubectl` and `flux` is available on DockerHub and GitHub:
* `docker.io/fluxcd/flux-cli:<version>`
* `ghcr.io/fluxcd/flux-cli:<version>`
Verify that your cluster satisfies the prerequisites with: Verify that your cluster satisfies the prerequisites with:
```sh ```sh
@@ -42,36 +47,61 @@ flux check --pre
Using the `flux bootstrap` command you can install Flux on a Using the `flux bootstrap` command you can install Flux on a
Kubernetes cluster and configure it to manage itself from a Git Kubernetes cluster and configure it to manage itself from a Git
repository. repository.
The bootstrap creates a Git repository if one doesn't exist and
commits the Flux components manifests to the main branch. Then it
configures the target cluster to synchronize with that repository by
setting up SSH deploy keys.
If the Flux components are present on the cluster, the bootstrap If the Flux components are present on the cluster, the bootstrap
command will perform an upgrade if needed. The bootstrap is command will perform an upgrade if needed. The bootstrap is
idempotent, it's safe to run the command as many times as you want. idempotent, it's safe to run the command as many times as you want.
You can choose what components to install and for which cluster with: The Flux component images are published to DockerHub and GitHub Container Registry
as [multi-arch container images](https://docs.docker.com/docker-for-mac/multi-arch/)
with support for Linux `amd64`, `arm64` and `armv7` (e.g. 32bit Raspberry Pi)
architectures.
If your Git provider is **GitHub**, **GitLab** or **Azure DevOps** please follow the specific bootstrap procedure:
* [GitHub.com and GitHub Enterprise](#github-and-github-enterprise)
* [GitLab.com and GitLab Enterprise](#gitlab-and-gitlab-enterprise)
* [Azure DevOps](../use-cases/azure.md#flux-installation-for-azure-devops)
### Generic Git Server
The `bootstrap git` command takes an existing Git repository, clones it and
commits the Flux components manifests to the specified branch. Then it
configures the target cluster to synchronize with that repository.
Run bootstrap for a Git repository and authenticate with your SSH agent:
```sh ```sh
flux bootstrap <GIT-PROVIDER> \ flux bootstrap git \
--components=source-controller,kustomize-controller,helm-controller,notification-controller \ --url=ssh://git@<host>/<org>/<repository> \
--components-extra=image-reflector-controller,image-automation-controller \ --branch=<my-branch> \
--path=clusters/my-cluster --path=clusters/my-cluster
``` ```
!!! hint "Multi-arch images" The above command will generate a SSH key (defaults to RSA 2048 but can be changed with `--ssh-key-algorithm`),
The component images are published as [multi-arch container images](https://docs.docker.com/docker-for-mac/multi-arch/) and it will prompt you to add the SSH public key as a deploy key to your repository.
with support for Linux `amd64`, `arm64` and `armv7` (e.g. 32bit Raspberry Pi)
architectures.
If you wish to install a specific version, use the Flux If you want to use your own SSH key, you can provide a **passwordless** private key using
[release tag](https://github.com/fluxcd/flux2/releases) e.g. `--version=v0.9.0`. `--private-key-file=<path/to/private.key>`.
This option can also be used if no SSH agent is available on your machine.
If you wish to deploy the Flux components onto !!! hint "Bootstrap options"
[tainted Kubernetes nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/), There are many options available when bootstrapping Flux, such as installing a subset of Flux components,
you can specify the toleration keys with `--toleration-keys=node.kubernetes.io/dedicated-to-flux`. setting the Kubernetes context, changing the Git author name and email, enabling Git submodules, and more.
To list all the available options run `flux bootstrap git --help`.
If your Git server doesn't support SSH, you can run bootstrap for Git over HTTPS:
```sh
flux bootstrap git \
--url=https://<host>/<org>/<repository> \
--username=<my-username> \
--password=<my-password> \
--token-auth=true \
--path=clusters/my-cluster
```
If your Git server uses a self-signed TLS certificate, you can specify the CA file with
`--ca-file=<path/to/ca.crt>`.
With `--path` you can configure the directory which will be used to reconcile the target cluster. With `--path` you can configure the directory which will be used to reconcile the target cluster.
To control multiple clusters from the same Git repository, you have to set a unique path per To control multiple clusters from the same Git repository, you have to set a unique path per
@@ -84,17 +114,13 @@ cluster e.g. `clusters/staging` and `clusters/production`:
│   ├── gotk-components.yaml │   ├── gotk-components.yaml
│   ├── gotk-sync.yaml │   ├── gotk-sync.yaml
│   └── kustomization.yaml │   └── kustomization.yaml
└── production-cluster # <- path=clusters/production └── production # <- path=clusters/production
└── flux-system └── flux-system
``` ```
After running bootstrap you can place Kubernetes YAMLs inside a dir under path After running bootstrap you can place Kubernetes YAMLs inside a dir under path
e.g. `clusters/staging/my-app`, and Flux will reconcile them on your cluster. e.g. `clusters/staging/my-app`, and Flux will reconcile them on your cluster.
!!! hint "Change the default branch"
If you wish to change the branch to something else than main, create the repository manually,
push a branch to origin and then use `flux bootstrap <GIT-PROVIDER> --branch=your-branch`.
For examples on how you can structure your Git repository see: For examples on how you can structure your Git repository see:
* [flux2-kustomize-helm-example](https://github.com/fluxcd/flux2-kustomize-helm-example) * [flux2-kustomize-helm-example](https://github.com/fluxcd/flux2-kustomize-helm-example)
@@ -102,6 +128,11 @@ For examples on how you can structure your Git repository see:
### GitHub and GitHub Enterprise ### GitHub and GitHub Enterprise
The `bootstrap github` command creates a GitHub repository if one doesn't exist and
commits the Flux components manifests to specified branch. Then it
configures the target cluster to synchronize with that repository by
setting up a SSH deploy key or by using token-based authentication.
Generate a [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) Generate a [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line)
that can create repositories by checking all permissions under `repo`. that can create repositories by checking all permissions under `repo`.
@@ -166,6 +197,11 @@ flux bootstrap github \
### GitLab and GitLab Enterprise ### GitLab and GitLab Enterprise
The `bootstrap gitlab` command creates a GitLab repository if one doesn't exist and
commits the Flux components manifests to specified branch. Then it
configures the target cluster to synchronize with that repository by
setting up a SSH deploy key or by using token-based authentication.
Generate a [personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) Generate a [personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html)
that grants complete read/write access to the GitLab API. that grants complete read/write access to the GitLab API.
@@ -274,162 +310,6 @@ the CLI will use the manifests embedded in its binary instead of downloading
them from GitHub. You can determine which version you'll be installing, them from GitHub. You can determine which version you'll be installing,
with `flux --version`. with `flux --version`.
### Generic Git Server
For other Git providers such as Bitbucket, Gogs, Gitea, Azure DevOps, etc
you can manually setup the repository and deploy key.
Create a Git repository and clone it locally:
```sh
git clone ssh://<host>/<org>/my-repository
cd my-repository
```
Create a directory inside the repository:
```sh
mkdir -p ./clusters/my-cluster/flux-system
```
Generate the Flux manifests with:
```sh
flux install \
--export > ./clusters/my-cluster/flux-system/gotk-components.yaml
```
Commit and push the manifest to the master branch:
```sh
git add -A && git commit -m "add components" && git push
```
Apply the manifests on your cluster:
```sh
kubectl apply -f ./clusters/my-cluster/flux-system/gotk-components.yaml
```
Verify that the controllers have started:
```sh
flux check
```
Create a `GitRepository` object on your cluster by specifying the SSH address of your repo:
```sh
flux create source git flux-system \
--url=ssh://git@<host>/<org>/<repository> \
--ssh-key-algorithm=ecdsa \
--ssh-ecdsa-curve=p521 \
--branch=master \
--interval=1m
```
You will be prompted to add a deploy key to your repository.
If you don't specify the SSH algorithm, then `flux` will generate an RSA 2048 bits key.
!!! hint "Azure DevOps"
Azure DevOps requires a non-default Git implementation (`libgit2`) to be enabled, so that the Git v2 protocol is supported.
Note that this implementation does not support shallow cloning, and it is therefore advised to only resort to this option if a
connection fails with the default configuration.
Azure DevOps [only supports RSA SSH keys](https://developercommunity.visualstudio.com/t/support-non-rsa-keys-for-ssh-authentication/365980),
you cannot use elliptic curve SSH keys like ecdsa or ed25519.
Here is how to specify the `libgit2` implementation and generate a proper RSA key:
```sh
flux create source git flux-system \
--git-implementation=libgit2 \
--ssh-key-algorithm=rsa \
--ssh-rsa-bits=4096 \
--url=ssh://git@ssh.dev.azure.com/v3/<org>/<project>/<repository> \
--branch=main \
--interval=1m
```
This config uses the `main` branch, but your repo may be older and need to specify `master` instead.
Note that unlike `git`, Flux does not support the
["shorter" scp-like syntax for the SSH protocol](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols#_the_ssh_protocol)
(e.g. `ssh.dev.azure.com:v3`).
Use the [RFC 3986 compatible syntax](https://tools.ietf.org/html/rfc3986#section-3) instead: `ssh.dev.azure.com/v3`.
The `flux create source git` command will prompt you to add a deploy key to your repository, but Azure DevOps
[does not support repository or org-specific deploy keys](https://developercommunity.visualstudio.com/t/allow-the-creation-of-ssh-deploy-keys-for-vsts-hos/365747).
You may add the deploy key to a user's personal SSH keys being mindful that removing them from the repo may revoke Flux's access.
As an alternative, create a machine-user whose sole purpose is to store credentials for automation.
Using a machine-user also has the benefit of being able to be read-only or restricted to specific repositories if that is needed.
If you wish to use Git over HTTPS, then generate a personal access token and supply it as the password:
```sh
flux create source git flux-system \
--git-implementation=libgit2 \
--url=https://dev.azure.com/<org>/<project>/_git/<repository> \
--branch=master \
--username=git \
--password=${AZ_PAT_TOKEN} \
--interval=1m
```
Please consult the [Azure DevOps documentation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page)
on how to generate personal access tokens for Git repositories.
Azure DevOps PAT's always have an expiration date, so be sure to have some process for renewing or updating these tokens.
Similar to the lack of repo-specific deploy keys, a user needs to generate a user-specific PAT.
If you are using a machine-user, you can generate a PAT or simply use the machine-user's password which does not expire.
If your Git server supports basic auth, you can set the URL to HTTPS and specify the credentials with:
```sh
flux create source git flux-system \
--url=https://<host>/<org>/my-repository \
--username=my-username \
--password=my-password \
--branch=master \
--interval=1m
```
Create a `Kustomization` object on your cluster:
```sh
flux create kustomization flux-system \
--source=flux-system \
--path="./clusters/my-cluster" \
--prune=true \
--interval=10m
```
Export both objects, generate a `kustomization.yaml`, commit and push the manifests to Git:
```sh
flux export source git flux-system \
> ./clusters/my-cluster/flux-system/gotk-sync.yaml
flux export kustomization flux-system \
>> ./clusters/my-cluster/flux-system/gotk-sync.yaml
cd ./clusters/my-cluster/flux-system && kustomize create --autodetect
git add -A && git commit -m "add sync manifests" && git push
```
To upgrade the Flux components to a newer version, download the latest `flux` binary,
run the install command and commit the changes:
```sh
flux install \
--export > ./clusters/my-cluster/flux-system/gotk-components.yaml
git add -A && git commit -m "update flux" && git push
```
The source-controller will pull the changes on the cluster, then the kustomize-controller
will perform a rolling update of all Flux components including itself.
## Bootstrap with Terraform ## Bootstrap with Terraform
The bootstrap procedure can be implemented with Terraform using the Flux provider published on The bootstrap procedure can be implemented with Terraform using the Flux provider published on

View File

@@ -143,23 +143,13 @@ Multiple directories can use separate SOPS configs.
Contributors using the `sops` CLI to create and encrypt files Contributors using the `sops` CLI to create and encrypt files
won't have to worry about specifying the proper key for the target cluster or namespace. won't have to worry about specifying the proper key for the target cluster or namespace.
`encrypted_regex` helps encrypt the the proper `data` and `stringData` fields for Secrets. `encrypted_regex` helps encrypt the `data` and `stringData` fields for Secrets.
You may wish to add other fields if you are encrypting other types of Objects. You may wish to add other fields if you are encrypting other types of Objects.
!!! hint !!! hint
Note that you should encrypt only the `data` or `stringData` section. Encrypting the Kubernetes Note that you should encrypt only the `data` or `stringData` section. Encrypting the Kubernetes
secret metadata, kind or apiVersion is not supported by kustomize-controller. secret metadata, kind or apiVersion is not supported by kustomize-controller.
Ignore all `.sops.yaml` files in a [`.sourceignore`](../components/source/gitrepositories#excluding-files)
file at the root of your repo.
```sh
touch .sourceignore
echo '**/.sops.yaml' >> .sourceignore
```
You can now commit your SOPS config.
## Encrypt secrets ## Encrypt secrets
Generate a Kubernetes secret manifest with kubectl: Generate a Kubernetes secret manifest with kubectl:

View File

@@ -45,7 +45,7 @@ spec:
name: slack-url name: slack-url
``` ```
The provider type can be `slack`, `msteams`, `discord`, `rocket`, `github`, `gitlab` or `generic`. The provider type can be `slack`, `msteams`, `discord`, `rocket`, `googlechat`, `webex`, `sentry` or `generic`.
When type `generic` is specified, the notification controller will post the incoming When type `generic` is specified, the notification controller will post the incoming
[event](../components/notification/event.md) in JSON format to the webhook address. [event](../components/notification/event.md) in JSON format to the webhook address.

View File

@@ -6,9 +6,10 @@ It's important to follow some guidelines when installing Flux on AKS.
### CNI and Network Policy ### CNI and Network Policy
Previously, there has been an issue with Flux and Network Policy on AKS. ([Upstream Azure Issue](https://github.com/Azure/AKS/issues/2031)) ([Flux Issue](https://github.com/fluxcd/flux2/issues/703)) Previously, there has been an issue with Flux and Network Policy on AKS.
If you ensure your AKS cluster is upgraded, and your Nodes have been restarted with the most recent Node images, this could ([Upstream Azure Issue](https://github.com/Azure/AKS/issues/2031)) ([Flux Issue](https://github.com/fluxcd/flux2/issues/703))
resolve flux reconciliation failures where source-controller is unreachable. If you ensure your AKS cluster is upgraded, and your Nodes have been restarted with the most recent Node images,
this could resolve flux reconciliation failures where source-controller is unreachable.
Using `--network-plugin=azure --network-policy=calico` has been tested to work properly. Using `--network-plugin=azure --network-policy=calico` has been tested to work properly.
This issue only affects you if you are using `--network-policy` on AKS, which is not a default option. This issue only affects you if you are using `--network-policy` on AKS, which is not a default option.
@@ -21,24 +22,23 @@ Depending on the features you are interested in using with Flux, you may want to
With [AAD Pod-Identity](https://azure.github.io/aad-pod-identity/docs/), we can create Pods that have their own With [AAD Pod-Identity](https://azure.github.io/aad-pod-identity/docs/), we can create Pods that have their own
cloud credentials for accessing Azure services like Azure Container Registry(ACR) and Azure Key Vault(AKV). cloud credentials for accessing Azure services like Azure Container Registry(ACR) and Azure Key Vault(AKV).
If you do not use AAD Pod-Identity, you'll need to manage and store Service Principal credentials in K8s Secrets, to integrate Flux If you do not use AAD Pod-Identity, you'll need to manage and store Service Principal credentials
with other Azure Services. in K8s Secrets, to integrate Flux with other Azure Services.
As a pre-requisite, your cluster must have `--enable-managed-identity` configured. As a pre-requisite, your cluster must have `--enable-managed-identity` configured.
This software can be [installed via Helm](https://azure.github.io/aad-pod-identity/docs/getting-started/installation/) (unmanaged by Azure). This software can be [installed via Helm](https://azure.github.io/aad-pod-identity/docs/getting-started/installation/)
Use Flux's `HelmRepository` and `HelmRelease` object to manage the aad-pod-identity installation from a bootstrap repository and keep it up to date. (unmanaged by Azure).
Use Flux's `HelmRepository` and `HelmRelease` object to manage the aad-pod-identity installation
from a bootstrap repository and keep it up to date.
!!! note !!! note
As an alternative to Helm, the `--enable-aad-pod-identity` flag for the `az aks create` is currently in Preview. As an alternative to Helm, the `--enable-aad-pod-identity` flag for the `az aks create` is currently in Preview.
Follow the Azure guide for [Creating an AKS cluster with AAD Pod Identity](https://docs.microsoft.com/en-us/azure/aks/use-azure-ad-pod-identity) if you would like to enable this feature with the Azure CLI. Follow the Azure guide for [Creating an AKS cluster with AAD Pod Identity](https://docs.microsoft.com/en-us/azure/aks/use-azure-ad-pod-identity)
if you would like to enable this feature with the Azure CLI.
### Cluster Creation ### Cluster Creation
!!! info
When working with the Azure CLI, it can help to set a default `location`, `group`, and `acr`.
See `az configure --help`, `az configure --list-defaults`, and `az configure --defaults key=value`
The following creates an AKS cluster with some minimal configuration that will work well with Flux: The following creates an AKS cluster with some minimal configuration that will work well with Flux:
```sh ```sh
@@ -50,37 +50,157 @@ az aks create \
--name="my-cluster" --name="my-cluster"
``` ```
## Flux Installation with Azure DevOps Repos !!! info
When working with the Azure CLI, it can help to set a default `location`, `group`, and `acr`.
See `az configure --help`, `az configure --list-defaults`, and `az configure --defaults key=value`.
Ensure you can login to [dev.azure.com](https://dev.azure.com) for your proper organization, and create a new repo to hold your ## Flux Installation for Azure DevOps
flux install and other necessary config.
There is no bootstrap provider currently for Azure DevOps Repos, Ensure you can login to [dev.azure.com](https://dev.azure.com) for your proper organization,
but you can clone your Azure Repo, then use the [Generic Git Server](../guides/installation.md#generic-git-server) and create a new repository to hold your Flux install and other Kubernetes resources.
guide to manually bootstrap Flux. (It must be a Git repo; TFVC Repos are not supported by source-controller)
Take note of the Azure DevOps specific section within the guide.
If you use the generated SSH deploy key from `flux create source git`, ensure it is an RSA key (not an elliptic curve). Clone the Git repository locally:
Make sure to use the `libgit2` provider for all `GitRepository` objects fetching from Azure Repos since they use Git Protocol v2.
Whether you're using the generated SSH deploy key or a Personal Access Token, the credentials used by ```sh
Flux will need to be owned by an Azure DevOps User with access to the repo. git clone ssh://git@ssh.dev.azure.com/v3/<org>/<project>/<my-repository>
Consider creating a machine-user and granting it granular permissions to access what's needed. cd my-repository
This allows changing user access without affecting Flux. ```
Since PAT's expire on Azure DevOps, using a machine-user's login password to authenticate with HTTPS and `libgit2`
can be a good option that avoids the need to renew the credential while also having the benefit of more granular permissions. Create a directory inside the repository:
```sh
mkdir -p ./clusters/my-cluster/flux-system
```
Download the [Flux CLI](../guides/installation.md#install-the-flux-cli) and generate the manifests with:
```sh
flux install \
--export > ./clusters/my-cluster/flux-system/gotk-components.yaml
```
Commit and push the manifest to the master branch:
```sh
git add -A && git commit -m "add components" && git push
```
Apply the manifests on your cluster:
```sh
kubectl apply -f ./clusters/my-cluster/flux-system/gotk-components.yaml
```
Verify that the controllers have started:
```sh
flux check
```
Create a `GitRepository` object on your cluster by specifying the SSH address of your repo:
```sh
flux create source git flux-system \
--git-implementation=libgit2 \
--url=ssh://git@ssh.dev.azure.com/v3/<org>/<project>/<repository> \
--branch=<branch> \
--ssh-key-algorithm=rsa \
--ssh-rsa-bits=4096 \
--interval=1m
```
The above command will prompt you to add a deploy key to your repository, but Azure DevOps
[does not support repository or org-specific deploy keys](https://developercommunity.visualstudio.com/t/allow-the-creation-of-ssh-deploy-keys-for-vsts-hos/365747).
You may add the deploy key to a user's personal SSH keys, but take note that
revoking the user's access to the repository will also revoke Flux's access.
The better alternative is to create a machine-user whose sole purpose is
to store credentials for automation.
Using a machine-user also has the benefit of being able to be read-only or
restricted to specific repositories if this is needed.
!!! note
Unlike `git`, Flux does not support the
["shorter" scp-like syntax for the SSH protocol](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols#_the_ssh_protocol)
(e.g. `ssh.dev.azure.com:v3`).
Use the [RFC 3986 compatible syntax](https://tools.ietf.org/html/rfc3986#section-3) instead: `ssh.dev.azure.com/v3`.
If you wish to use Git over HTTPS, then generate a personal access token and supply it as the password:
```sh
flux create source git flux-system \
--git-implementation=libgit2 \
--url=https://dev.azure.com/<org>/<project>/_git/<repository> \
--branch=main \
--username=git \
--password=${AZ_PAT_TOKEN} \
--interval=1m
```
Please consult the [Azure DevOps documentation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page)
on how to generate personal access tokens for Git repositories.
Azure DevOps PAT's always have an expiration date, so be sure to have some process for renewing or updating these tokens.
Similar to the lack of repo-specific deploy keys, a user needs to generate a user-specific PAT.
If you are using a machine-user, you can generate a PAT or simply use the machine-user's password which does not expire.
Create a `Kustomization` object on your cluster:
```sh
flux create kustomization flux-system \
--source=flux-system \
--path="./clusters/my-cluster" \
--prune=true \
--interval=10m
```
Export both objects, generate a `kustomization.yaml`, commit and push the manifests to Git:
```sh
flux export source git flux-system \
> ./clusters/my-cluster/flux-system/gotk-sync.yaml
flux export kustomization flux-system \
>> ./clusters/my-cluster/flux-system/gotk-sync.yaml
cd ./clusters/my-cluster/flux-system && kustomize create --autodetect
git add -A && git commit -m "add sync manifests" && git push
```
Wait for Flux to reconcile your previous commit with:
```sh
watch flux get kustomization flux-system
```
### Flux Upgrade
To upgrade the Flux components to a newer version, download the latest `flux` binary,
run the install command in your repository root, commit and push the changes:
```sh
flux install \
--export > ./clusters/my-cluster/flux-system/gotk-components.yaml
git add -A && git commit -m "Upgrade to $(flux -v)" && git push
```
The [source-controller](../components/source/controller.md) will pull the changes on the cluster,
then [kustomize-controller](../components/source/controller.md)
will perform a rolling update of all Flux components including itself.
## Helm Repositories on Azure Container Registry ## Helm Repositories on Azure Container Registry
The Flux `HelmRepository` object currently supports [Chart Repositories](https://helm.sh/docs/topics/chart_repository/) The Flux `HelmRepository` object currently supports
[Chart Repositories](https://helm.sh/docs/topics/chart_repository/)
as well as fetching `HelmCharts` from paths in `GitRepository` sources. as well as fetching `HelmCharts` from paths in `GitRepository` sources.
Azure Container Registry has a sub-command ([`az acr helm`](https://docs.microsoft.com/en-us/cli/azure/acr/helm)) for working with Azure Container Registry has a sub-command ([`az acr helm`](https://docs.microsoft.com/en-us/cli/azure/acr/helm))
ACR-Hosted Chart Repositories, but it is deprecated. for working with ACR-Hosted Chart Repositories, but it is deprecated.
If you are using these deprecated Azure Chart Repositories, you can use Flux `HelmRepository` objects with them. If you are using these deprecated Azure Chart Repositories,
you can use Flux `HelmRepository` objects with them.
[Newer ACR Helm documentation](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-helm-repos) suggests [Newer ACR Helm documentation](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-helm-repos)
using ACR as an experimental [Helm OCI Registry](https://helm.sh/docs/topics/registries/). suggests using ACR as an experimental [Helm OCI Registry](https://helm.sh/docs/topics/registries/).
This will not work with Flux, because using Charts from OCI Registries is not yet supported. This will not work with Flux, because using Charts from OCI Registries is not yet supported.
## Secrets Management with SOPS and Azure Key Vault ## Secrets Management with SOPS and Azure Key Vault
@@ -108,5 +228,6 @@ flux install \
Follow the [Image Update Automation Guide](../guides/image-update.md) and see the Follow the [Image Update Automation Guide](../guides/image-update.md) and see the
[ACR specific section](../guides/image-update.md#azure-container-registry) for more details. [ACR specific section](../guides/image-update.md#azure-container-registry) for more details.
Your AKS cluster's configuration can also be updated to [allow the kubelets to pull images from ACR](https://docs.microsoft.com/en-us/azure/aks/cluster-container-registry-integration) Your AKS cluster's configuration can also be updated to
[allow the kubelets to pull images from ACR](https://docs.microsoft.com/en-us/azure/aks/cluster-container-registry-integration)
without ImagePullSecrets as an optional, complimentary step. without ImagePullSecrets as an optional, complimentary step.

14
go.mod
View File

@@ -5,23 +5,25 @@ go 1.16
require ( require (
github.com/Masterminds/semver/v3 v3.1.0 github.com/Masterminds/semver/v3 v3.1.0
github.com/cyphar/filepath-securejoin v0.2.2 github.com/cyphar/filepath-securejoin v0.2.2
github.com/fluxcd/go-git-providers v0.0.3
github.com/fluxcd/helm-controller/api v0.9.0 github.com/fluxcd/helm-controller/api v0.9.0
github.com/fluxcd/image-automation-controller/api v0.7.0 github.com/fluxcd/image-automation-controller/api v0.8.0
github.com/fluxcd/image-reflector-controller/api v0.7.1 github.com/fluxcd/image-reflector-controller/api v0.8.0
github.com/fluxcd/kustomize-controller/api v0.10.0 github.com/fluxcd/kustomize-controller/api v0.11.0
github.com/fluxcd/notification-controller/api v0.11.0 github.com/fluxcd/notification-controller/api v0.12.0
github.com/fluxcd/pkg/apis/meta v0.8.0 github.com/fluxcd/pkg/apis/meta v0.8.0
github.com/fluxcd/pkg/git v0.3.0
github.com/fluxcd/pkg/runtime v0.10.1 github.com/fluxcd/pkg/runtime v0.10.1
github.com/fluxcd/pkg/ssh v0.0.5 github.com/fluxcd/pkg/ssh v0.0.5
github.com/fluxcd/pkg/untar v0.0.5 github.com/fluxcd/pkg/untar v0.0.5
github.com/fluxcd/pkg/version v0.0.1 github.com/fluxcd/pkg/version v0.0.1
github.com/fluxcd/source-controller/api v0.10.0 github.com/fluxcd/source-controller/api v0.11.0
github.com/go-git/go-git/v5 v5.1.0
github.com/google/go-containerregistry v0.2.0 github.com/google/go-containerregistry v0.2.0
github.com/manifoldco/promptui v0.7.0 github.com/manifoldco/promptui v0.7.0
github.com/olekukonko/tablewriter v0.0.4 github.com/olekukonko/tablewriter v0.0.4
github.com/spf13/cobra v1.1.1 github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/xanzy/go-gitlab v0.43.0 // indirect
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0
k8s.io/api v0.20.2 k8s.io/api v0.20.2
k8s.io/apiextensions-apiserver v0.20.2 k8s.io/apiextensions-apiserver v0.20.2

40
go.sum
View File

@@ -188,22 +188,22 @@ github.com/evanphx/json-patch/v5 v5.1.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2Vvl
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fluxcd/go-git-providers v0.0.3 h1:pquQvTpd1a4V1efPyZWuVPeIKrTgV8QRoDY0VGH+qiw=
github.com/fluxcd/go-git-providers v0.0.3/go.mod h1:iaXf3nEq8MB/LzxfbNcCl48sAtIReUU7jqjJ7CEnfFQ=
github.com/fluxcd/helm-controller/api v0.9.0 h1:L60KmCblTQo3UimgCzVQGe330tC+b15CrLozvhPNmJU= github.com/fluxcd/helm-controller/api v0.9.0 h1:L60KmCblTQo3UimgCzVQGe330tC+b15CrLozvhPNmJU=
github.com/fluxcd/helm-controller/api v0.9.0/go.mod h1:HIWSF3n1QU3hdqjQMFizFUZVr1uV+abmlGAEpB7vB9A= github.com/fluxcd/helm-controller/api v0.9.0/go.mod h1:HIWSF3n1QU3hdqjQMFizFUZVr1uV+abmlGAEpB7vB9A=
github.com/fluxcd/image-automation-controller/api v0.7.0 h1:mLaELYT52/FpZ93Mr+QMSK8UT0OBVQT4oA9kxO8NiEk= github.com/fluxcd/image-automation-controller/api v0.8.0 h1:lcyoYEQNorNijObqY9ZdSEurt2nXCXgFmx+PcM9W+Dw=
github.com/fluxcd/image-automation-controller/api v0.7.0/go.mod h1:7E2dCvoxmTkDttp+Hk8v9eoSK/CdFmFhRKInEXC3yVY= github.com/fluxcd/image-automation-controller/api v0.8.0/go.mod h1:7E2dCvoxmTkDttp+Hk8v9eoSK/CdFmFhRKInEXC3yVY=
github.com/fluxcd/image-reflector-controller/api v0.7.1 h1:Cng36D1J25WYZ0ZB6dwzDtGR9MIyIcSUMYxHpb0IYXA= github.com/fluxcd/image-reflector-controller/api v0.8.0 h1:6dTIcyJFApmcmT4HbitWBFvh6ne8jpp8QQseH9Q5hv0=
github.com/fluxcd/image-reflector-controller/api v0.7.1/go.mod h1:J18L71jiHYrAu2dg0tgOkOjP+GtQldC1oslhTeX0jqc= github.com/fluxcd/image-reflector-controller/api v0.8.0/go.mod h1:J18L71jiHYrAu2dg0tgOkOjP+GtQldC1oslhTeX0jqc=
github.com/fluxcd/kustomize-controller/api v0.10.0 h1:y4ps3oA1JiBEjzV7bS1NeT6b3kuaQNz5qX4BmHy1PD0= github.com/fluxcd/kustomize-controller/api v0.11.0 h1:pPeNh10vzlJQGmAMHLf2lJLVROJah2hu7JeUjjV4+74=
github.com/fluxcd/kustomize-controller/api v0.10.0/go.mod h1:uvt/PmSGP/n4SU5mRUa1r7HMwor7ofgBvJV5/rHNB54= github.com/fluxcd/kustomize-controller/api v0.11.0/go.mod h1:uvt/PmSGP/n4SU5mRUa1r7HMwor7ofgBvJV5/rHNB54=
github.com/fluxcd/notification-controller/api v0.11.0 h1:knpT/FRcIl1ara87UDEuhdzVSAmALF1IT1Cu7GQyrrU= github.com/fluxcd/notification-controller/api v0.12.0 h1:yzRnDIdIqT9iV9W0fexJwAROu4ZSNBpKEs5dFf3KaPk=
github.com/fluxcd/notification-controller/api v0.11.0/go.mod h1:CA02ixmq+kFN9eBkruvJClkMqffgRjYBMxym3AfhO6c= github.com/fluxcd/notification-controller/api v0.12.0/go.mod h1:CA02ixmq+kFN9eBkruvJClkMqffgRjYBMxym3AfhO6c=
github.com/fluxcd/pkg/apis/kustomize v0.0.1 h1:TkA80R0GopRY27VJqzKyS6ifiKIAfwBd7OHXtV3t2CI= github.com/fluxcd/pkg/apis/kustomize v0.0.1 h1:TkA80R0GopRY27VJqzKyS6ifiKIAfwBd7OHXtV3t2CI=
github.com/fluxcd/pkg/apis/kustomize v0.0.1/go.mod h1:JAFPfnRmcrAoG1gNiA8kmEXsnOBuDyZ/F5X4DAQcVV0= github.com/fluxcd/pkg/apis/kustomize v0.0.1/go.mod h1:JAFPfnRmcrAoG1gNiA8kmEXsnOBuDyZ/F5X4DAQcVV0=
github.com/fluxcd/pkg/apis/meta v0.8.0 h1:wqWpUsxhKHB1ZztcvOz+vnyhdKW9cWmjFp8Vci/XOdk= github.com/fluxcd/pkg/apis/meta v0.8.0 h1:wqWpUsxhKHB1ZztcvOz+vnyhdKW9cWmjFp8Vci/XOdk=
github.com/fluxcd/pkg/apis/meta v0.8.0/go.mod h1:yHuY8kyGHYz22I0jQzqMMGCcHViuzC/WPdo9Gisk8Po= github.com/fluxcd/pkg/apis/meta v0.8.0/go.mod h1:yHuY8kyGHYz22I0jQzqMMGCcHViuzC/WPdo9Gisk8Po=
github.com/fluxcd/pkg/git v0.3.0 h1:nrKZWZ/ymDevud3Wf1LEieO/QcNPnqz1/MrkZBFcg9o=
github.com/fluxcd/pkg/git v0.3.0/go.mod h1:ZwG0iLOqNSyNw6lsPIAO+v6+BqqCXyV+r1Oq6Lm+slg=
github.com/fluxcd/pkg/runtime v0.10.1 h1:NV0pe6lFzodKBIz0dT3xkoR0wJnTCicXwM/v/d5T0+Y= github.com/fluxcd/pkg/runtime v0.10.1 h1:NV0pe6lFzodKBIz0dT3xkoR0wJnTCicXwM/v/d5T0+Y=
github.com/fluxcd/pkg/runtime v0.10.1/go.mod h1:JD0eZIn5xkTeHHQUWXSqJPIh/ecO0d0qrUKbSVHnpnw= github.com/fluxcd/pkg/runtime v0.10.1/go.mod h1:JD0eZIn5xkTeHHQUWXSqJPIh/ecO0d0qrUKbSVHnpnw=
github.com/fluxcd/pkg/ssh v0.0.5 h1:rnbFZ7voy2JBlUfMbfyqArX2FYaLNpDhccGFC3qW83A= github.com/fluxcd/pkg/ssh v0.0.5 h1:rnbFZ7voy2JBlUfMbfyqArX2FYaLNpDhccGFC3qW83A=
@@ -212,8 +212,8 @@ github.com/fluxcd/pkg/untar v0.0.5 h1:UGI3Ch1UIEIaqQvMicmImL1s9npQa64DJ/ozqHKB7g
github.com/fluxcd/pkg/untar v0.0.5/go.mod h1:O6V9+rtl8c1mHBafgqFlJN6zkF1HS5SSYn7RpQJ/nfw= github.com/fluxcd/pkg/untar v0.0.5/go.mod h1:O6V9+rtl8c1mHBafgqFlJN6zkF1HS5SSYn7RpQJ/nfw=
github.com/fluxcd/pkg/version v0.0.1 h1:/8asQoDXSThz3csiwi4Qo8Zb6blAxLXbtxNgeMJ9bCg= github.com/fluxcd/pkg/version v0.0.1 h1:/8asQoDXSThz3csiwi4Qo8Zb6blAxLXbtxNgeMJ9bCg=
github.com/fluxcd/pkg/version v0.0.1/go.mod h1:WAF4FEEA9xyhngF8TDxg3UPu5fA1qhEYV8Pmi2Il01Q= github.com/fluxcd/pkg/version v0.0.1/go.mod h1:WAF4FEEA9xyhngF8TDxg3UPu5fA1qhEYV8Pmi2Il01Q=
github.com/fluxcd/source-controller/api v0.10.0 h1:Mu4cAXtZ7yq/rIrab81q1jbbhWwUxxAZ2R5bZ1m8AxE= github.com/fluxcd/source-controller/api v0.11.0 h1:I2suC+gHB3PuuuHXFeb3RaE9dJeiKew9x02xs9tLmyI=
github.com/fluxcd/source-controller/api v0.10.0/go.mod h1:Vuw+7UqEUUOdkKBfTUPHwaQgbn6LL2FwqPDx2UAk7NE= github.com/fluxcd/source-controller/api v0.11.0/go.mod h1:Vuw+7UqEUUOdkKBfTUPHwaQgbn6LL2FwqPDx2UAk7NE=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@@ -340,6 +340,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -384,8 +385,8 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-containerregistry v0.2.0 h1:cWFYx+kOkKdyOET0pcp7GMCmxj7da40StvluSuSXWCg= github.com/google/go-containerregistry v0.2.0 h1:cWFYx+kOkKdyOET0pcp7GMCmxj7da40StvluSuSXWCg=
github.com/google/go-containerregistry v0.2.0/go.mod h1:Ts3Wioz1r5ayWx8sS6vLcWltWcM1aqFjd/eVrkFhrWM= github.com/google/go-containerregistry v0.2.0/go.mod h1:Ts3Wioz1r5ayWx8sS6vLcWltWcM1aqFjd/eVrkFhrWM=
github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM= github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
@@ -422,6 +423,8 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@@ -491,6 +494,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@@ -511,6 +516,7 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ktrysmt/go-bitbucket v0.6.2/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
@@ -557,6 +563,7 @@ github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUb
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -591,6 +598,7 @@ github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
@@ -722,6 +730,7 @@ github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV
github.com/vdemeester/k8s-pkg-credentialprovider v1.18.1-0.20201019120933-f1d16962a4db/go.mod h1:grWy0bkr1XO6hqbaaCKaPXqkBVlMGHYG6PGykktwbJc= github.com/vdemeester/k8s-pkg-credentialprovider v1.18.1-0.20201019120933-f1d16962a4db/go.mod h1:grWy0bkr1XO6hqbaaCKaPXqkBVlMGHYG6PGykktwbJc=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/xanzy/go-gitlab v0.33.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
github.com/xanzy/go-gitlab v0.43.0 h1:rpOZQjxVJGW/ch+Jy4j7W4o7BB1mxkXJNVGuplZ7PUs= github.com/xanzy/go-gitlab v0.43.0 h1:rpOZQjxVJGW/ch+Jy4j7W4o7BB1mxkXJNVGuplZ7PUs=
github.com/xanzy/go-gitlab v0.43.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/go-gitlab v0.43.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
@@ -858,6 +867,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -874,6 +884,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1021,6 +1032,7 @@ google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

View File

@@ -0,0 +1,198 @@
/*
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 bootstrap
import (
"context"
"errors"
"fmt"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
)
var (
ErrReconciledWithWarning = errors.New("reconciled with warning")
)
type Reconciler interface {
// ReconcileComponents reconciles the components by generating the
// manifests with the provided values, committing them to Git and
// pushing to remote if there are any changes, and applying them
// to the cluster.
ReconcileComponents(ctx context.Context, manifestsBase string, options install.Options) error
// ReconcileSourceSecret reconciles the source secret by generating
// a new secret with the provided values if the secret does not
// already exists on the cluster, or if any of the configuration
// options changed.
ReconcileSourceSecret(ctx context.Context, options sourcesecret.Options) error
// ReconcileSyncConfig reconciles the sync configuration by generating
// the sync manifests with the provided values, committing them to Git
// and pushing to remote if there are any changes.
ReconcileSyncConfig(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error
// ConfirmHealthy confirms that the components and extra components in
// install.Options are healthy.
ConfirmHealthy(ctx context.Context, options install.Options, timeout time.Duration) error
}
type RepositoryReconciler interface {
// ReconcileRepository reconciles an external Git repository.
ReconcileRepository(ctx context.Context) error
}
type PostGenerateSecretFunc func(ctx context.Context, secret corev1.Secret, options sourcesecret.Options) error
func Run(ctx context.Context, reconciler Reconciler, manifestsBase string,
installOpts install.Options, secretOpts sourcesecret.Options, syncOpts sync.Options,
pollInterval, timeout time.Duration) error {
var err error
if r, ok := reconciler.(RepositoryReconciler); ok {
if err = r.ReconcileRepository(ctx); err != nil && !errors.Is(err, ErrReconciledWithWarning) {
return err
}
}
if err := reconciler.ReconcileComponents(ctx, manifestsBase, installOpts); err != nil {
return err
}
if err := reconciler.ReconcileSourceSecret(ctx, secretOpts); err != nil {
return err
}
if err := reconciler.ReconcileSyncConfig(ctx, syncOpts, pollInterval, timeout); err != nil {
return err
}
if err := reconciler.ConfirmHealthy(ctx, installOpts, timeout); err != nil {
return err
}
return err
}
func mustInstallManifests(ctx context.Context, kube client.Client, namespace string) bool {
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: namespace,
}
var k kustomizev1.Kustomization
if err := kube.Get(ctx, namespacedName, &k); err != nil {
return true
}
return k.Status.LastAppliedRevision == ""
}
func secretExists(ctx context.Context, kube client.Client, objKey client.ObjectKey) (bool, error) {
if err := kube.Get(ctx, objKey, &corev1.Secret{}); err != nil {
if apierr.IsNotFound(err) {
return false, nil
}
return false, err
}
return true, nil
}
func reconcileSecret(ctx context.Context, kube client.Client, secret corev1.Secret) error {
objKey := client.ObjectKeyFromObject(&secret)
var existing corev1.Secret
err := kube.Get(ctx, objKey, &existing)
if err != nil {
if apierr.IsNotFound(err) {
return kube.Create(ctx, &secret)
}
return err
}
existing.StringData = secret.StringData
return kube.Update(ctx, &existing)
}
func kustomizationPathDiffers(ctx context.Context, kube client.Client, objKey client.ObjectKey, path string) (string, error) {
var k kustomizev1.Kustomization
if err := kube.Get(ctx, objKey, &k); err != nil {
if apierr.IsNotFound(err) {
return "", nil
}
return "", err
}
normalizePath := func(p string) string {
return fmt.Sprintf("./%s", strings.TrimPrefix(p, "./"))
}
if normalizePath(path) == normalizePath(k.Spec.Path) {
return "", nil
}
return k.Spec.Path, nil
}
func kustomizationReconciled(ctx context.Context, kube client.Client, objKey client.ObjectKey,
kustomization *kustomizev1.Kustomization, expectRevision string) func() (bool, error) {
return func() (bool, error) {
if err := kube.Get(ctx, objKey, kustomization); err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if kustomization.Generation != kustomization.Status.ObservedGeneration {
return false, nil
}
// Confirm the given revision has been attempted by the controller
if kustomization.Status.LastAttemptedRevision != expectRevision {
return false, nil
}
// Confirm the resource is healthy
if c := apimeta.FindStatusCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}
func retry(retries int, wait time.Duration, fn func() error) (err error) {
for i := 0; ; i++ {
err = fn()
if err == nil {
return
}
if i >= retries {
break
}
time.Sleep(wait)
}
return err
}

View File

@@ -0,0 +1,330 @@
/*
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 bootstrap
import (
"context"
"fmt"
"path/filepath"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/yaml"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/fluxcd/flux2/internal/bootstrap/git"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/log"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/kustomization"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
"github.com/fluxcd/flux2/pkg/status"
)
type PlainGitBootstrapper struct {
url string
branch string
author git.Author
commitMessageAppendix string
kubeconfig string
kubecontext string
postGenerateSecret []PostGenerateSecretFunc
git git.Git
kube client.Client
logger log.Logger
}
type GitOption interface {
applyGit(b *PlainGitBootstrapper)
}
func WithRepositoryURL(url string) GitOption {
return repositoryURLOption(url)
}
type repositoryURLOption string
func (o repositoryURLOption) applyGit(b *PlainGitBootstrapper) {
b.url = string(o)
}
func WithPostGenerateSecretFunc(callback PostGenerateSecretFunc) GitOption {
return postGenerateSecret(callback)
}
type postGenerateSecret PostGenerateSecretFunc
func (o postGenerateSecret) applyGit(b *PlainGitBootstrapper) {
b.postGenerateSecret = append(b.postGenerateSecret, PostGenerateSecretFunc(o))
}
func NewPlainGitProvider(git git.Git, kube client.Client, opts ...GitOption) (*PlainGitBootstrapper, error) {
b := &PlainGitBootstrapper{
git: git,
kube: kube,
}
for _, opt := range opts {
opt.applyGit(b)
}
return b, nil
}
func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifestsBase string, options install.Options) error {
// Clone if not already
if _, err := b.git.Status(); err != nil {
if err != git.ErrNoGitRepository {
return err
}
b.logger.Actionf("cloning branch %q from Git repository %q", b.branch, b.url)
var cloned bool
if err = retry(1, 2*time.Second, func() (err error) {
cloned, err = b.git.Clone(ctx, b.url, b.branch)
return
}); err != nil {
return fmt.Errorf("failed to clone repository: %w", err)
}
if cloned {
b.logger.Successf("cloned repository")
}
}
// Generate component manifests
b.logger.Actionf("generating component manifests")
manifests, err := install.Generate(options, manifestsBase)
if err != nil {
return fmt.Errorf("component manifest generation failed: %w", err)
}
b.logger.Successf("generated component manifests")
// Write manifest to Git repository
if err = b.git.Write(manifests.Path, strings.NewReader(manifests.Content)); err != nil {
return fmt.Errorf("failed to write manifest %q: %w", manifests.Path, err)
}
// Git commit generated
commitMsg := fmt.Sprintf("Add Flux %s component manifests", options.Version)
if b.commitMessageAppendix != "" {
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix
}
commit, err := b.git.Commit(git.Commit{
Author: b.author,
Message: commitMsg,
})
if err != nil && err != git.ErrNoStagedFiles {
return fmt.Errorf("failed to commit sync manifests: %w", err)
}
if err == nil {
b.logger.Successf("committed sync manifests to %q (%q)", b.branch, commit)
b.logger.Actionf("pushing component manifests to %q", b.url)
if err = b.git.Push(ctx); err != nil {
return fmt.Errorf("failed to push manifests: %w", err)
}
} else {
b.logger.Successf("component manifests are up to date")
}
// Conditionally install manifests
if mustInstallManifests(ctx, b.kube, options.Namespace) {
b.logger.Actionf("installing components in %q namespace", options.Namespace)
kubectlArgs := []string{"apply", "-f", filepath.Join(b.git.Path(), manifests.Path)}
if _, err = utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, b.kubeconfig, b.kubecontext, kubectlArgs...); err != nil {
return err
}
b.logger.Successf("installed components")
}
b.logger.Successf("reconciled components")
return nil
}
func (b *PlainGitBootstrapper) ReconcileSourceSecret(ctx context.Context, options sourcesecret.Options) error {
// Determine if there is an existing secret
secretKey := client.ObjectKey{Name: options.Name, Namespace: options.Namespace}
b.logger.Actionf("determining if source secret %q exists", secretKey)
ok, err := secretExists(ctx, b.kube, secretKey)
if err != nil {
return fmt.Errorf("failed to determine if deploy key secret exists: %w", err)
}
// Return early if exists and no custom config is passed
if ok && len(options.CAFilePath+options.PrivateKeyPath+options.Username+options.Password) == 0 {
b.logger.Successf("source secret up to date")
return nil
}
// Generate source secret
b.logger.Actionf("generating source secret")
manifest, err := sourcesecret.Generate(options)
if err != nil {
return err
}
var secret corev1.Secret
if err := yaml.Unmarshal([]byte(manifest.Content), &secret); err != nil {
return fmt.Errorf("failed to unmarshal generated source secret manifest: %w", err)
}
for _, callback := range b.postGenerateSecret {
if err = callback(ctx, secret, options); err != nil {
return err
}
}
// Apply source secret
b.logger.Actionf("applying source secret %q", secretKey)
if err = reconcileSecret(ctx, b.kube, secret); err != nil {
return err
}
b.logger.Successf("reconciled source secret")
return nil
}
func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error {
// Confirm that sync configuration does not overwrite existing config
if curPath, err := kustomizationPathDiffers(ctx, b.kube, client.ObjectKey{Name: options.Name, Namespace: options.Namespace}, options.TargetPath); err != nil {
return fmt.Errorf("failed to determine if sync configuration would overwrite existing Kustomization: %w", err)
} else if curPath != "" {
return fmt.Errorf("sync path configuration (%q) would overwrite path (%q) of existing Kustomization", options.TargetPath, curPath)
}
// Clone if not already
if _, err := b.git.Status(); err != nil {
if err == git.ErrNoGitRepository {
b.logger.Actionf("cloning branch %q from Git repository %q", b.branch, b.url)
var cloned bool
if err = retry(1, 2*time.Second, func() (err error) {
cloned, err = b.git.Clone(ctx, b.url, b.branch)
return
}); err != nil {
return fmt.Errorf("failed to clone repository: %w", err)
}
if cloned {
b.logger.Successf("cloned repository")
}
}
return err
}
// Generate sync manifests and write to Git repository
b.logger.Actionf("generating sync manifests")
manifests, err := sync.Generate(options)
if err != nil {
return fmt.Errorf("sync manifests generation failed: %w", err)
}
if err = b.git.Write(manifests.Path, strings.NewReader(manifests.Content)); err != nil {
return fmt.Errorf("failed to write manifest %q: %w", manifests.Path, err)
}
kusManifests, err := kustomization.Generate(kustomization.Options{
FileSystem: filesys.MakeFsOnDisk(),
BaseDir: b.git.Path(),
TargetPath: filepath.Dir(manifests.Path),
})
if err != nil {
return fmt.Errorf("kustomization.yaml generation failed: %w", err)
}
if err = b.git.Write(kusManifests.Path, strings.NewReader(kusManifests.Content)); err != nil {
return fmt.Errorf("failed to write manifest %q: %w", kusManifests.Path, err)
}
b.logger.Successf("generated sync manifests")
// Git commit generated
commitMsg := fmt.Sprintf("Add Flux sync manifests")
if b.commitMessageAppendix != "" {
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix
}
commit, err := b.git.Commit(git.Commit{
Author: b.author,
Message: commitMsg,
})
if err != nil && err != git.ErrNoStagedFiles {
return fmt.Errorf("failed to commit sync manifests: %w", err)
}
if err == nil {
b.logger.Successf("committed sync manifests to %q (%q)", b.branch, commit)
b.logger.Actionf("pushing sync manifests to %q", b.url)
if err = b.git.Push(ctx); err != nil {
return fmt.Errorf("failed to push sync manifests: %w", err)
}
} else {
b.logger.Successf("sync manifests are up to date")
}
// Apply to cluster
b.logger.Actionf("applying sync manifests")
kubectlArgs := []string{"apply", "-k", filepath.Join(b.git.Path(), filepath.Dir(kusManifests.Path))}
if _, err = utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, b.kubeconfig, b.kubecontext, kubectlArgs...); err != nil {
return err
}
b.logger.Successf("applied sync manifests")
// Wait till Kustomization is reconciled
var k kustomizev1.Kustomization
expectRevision := fmt.Sprintf("%s/%s", options.Branch, commit)
if err := wait.PollImmediate(pollInterval, timeout, kustomizationReconciled(
ctx, b.kube, client.ObjectKey{Name: options.Name, Namespace: options.Namespace}, &k, expectRevision),
); err != nil {
return fmt.Errorf("failed waiting for Kustomization: %w", err)
}
b.logger.Successf("reconciled sync configuration")
return nil
}
func (b *PlainGitBootstrapper) ConfirmHealthy(ctx context.Context, install install.Options, timeout time.Duration) error {
cfg, err := utils.KubeConfig(b.kubeconfig, b.kubecontext)
if err != nil {
return err
}
checker, err := status.NewStatusChecker(cfg, 2*time.Second, timeout, b.logger)
if err != nil {
return err
}
var components = install.Components
components = append(components, install.ComponentsExtra...)
var identifiers []object.ObjMetadata
for _, component := range components {
identifiers = append(identifiers, object.ObjMetadata{
Namespace: install.Namespace,
Name: component,
GroupKind: schema.GroupKind{Group: "apps", Kind: "Deployment"},
})
}
b.logger.Actionf("confirming components are healthy")
if err := checker.Assess(identifiers...); err != nil {
return err
}
b.logger.Successf("all components are healthy")
return nil
}

View File

@@ -0,0 +1,546 @@
/*
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 bootstrap
import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/go-git-providers/gitprovider"
"github.com/fluxcd/flux2/internal/bootstrap/git"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
)
type GitProviderBootstrapper struct {
*PlainGitBootstrapper
owner string
repository string
personal bool
description string
defaultBranch string
visibility string
teams map[string]string
readWriteKey bool
bootstrapTransportType string
syncTransportType string
sshHostname string
provider gitprovider.Client
}
func NewGitProviderBootstrapper(git git.Git, provider gitprovider.Client, kube client.Client, opts ...GitProviderOption) (*GitProviderBootstrapper, error) {
b := &GitProviderBootstrapper{
PlainGitBootstrapper: &PlainGitBootstrapper{
git: git,
kube: kube,
},
bootstrapTransportType: "https",
syncTransportType: "ssh",
provider: provider,
}
b.PlainGitBootstrapper.postGenerateSecret = append(b.PlainGitBootstrapper.postGenerateSecret, b.reconcileDeployKey)
for _, opt := range opts {
opt.applyGitProvider(b)
}
return b, nil
}
type GitProviderOption interface {
applyGitProvider(b *GitProviderBootstrapper)
}
func WithProviderRepository(owner, repository string, personal bool) GitProviderOption {
return providerRepositoryOption{
owner: owner,
repository: repository,
personal: personal,
}
}
type providerRepositoryOption struct {
owner string
repository string
personal bool
}
func (o providerRepositoryOption) applyGitProvider(b *GitProviderBootstrapper) {
b.owner = o.owner
b.repository = o.repository
b.personal = o.personal
}
func WithProviderRepositoryConfig(description, defaultBranch, visibility string) GitProviderOption {
return providerRepositoryConfigOption{
description: description,
defaultBranch: defaultBranch,
visibility: visibility,
}
}
type providerRepositoryConfigOption struct {
description string
defaultBranch string
visibility string
}
func (o providerRepositoryConfigOption) applyGitProvider(b *GitProviderBootstrapper) {
b.description = o.description
b.defaultBranch = o.defaultBranch
b.visibility = o.visibility
}
func WithProviderTeamPermissions(teams map[string]string) GitProviderOption {
return providerRepositoryTeamPermissionsOption(teams)
}
type providerRepositoryTeamPermissionsOption map[string]string
func (o providerRepositoryTeamPermissionsOption) applyGitProvider(b *GitProviderBootstrapper) {
b.teams = o
}
func WithReadWriteKeyPermissions(b bool) GitProviderOption {
return withReadWriteKeyPermissionsOption(b)
}
type withReadWriteKeyPermissionsOption bool
func (o withReadWriteKeyPermissionsOption) applyGitProvider(b *GitProviderBootstrapper) {
b.readWriteKey = bool(o)
}
func WithBootstrapTransportType(protocol string) GitProviderOption {
return bootstrapTransportTypeOption(protocol)
}
type bootstrapTransportTypeOption string
func (o bootstrapTransportTypeOption) applyGitProvider(b *GitProviderBootstrapper) {
b.bootstrapTransportType = string(o)
}
func WithSyncTransportType(protocol string) GitProviderOption {
return syncProtocolOption(protocol)
}
type syncProtocolOption string
func (o syncProtocolOption) applyGitProvider(b *GitProviderBootstrapper) {
b.syncTransportType = string(o)
}
func WithSSHHostname(hostname string) GitProviderOption {
return sshHostnameOption(hostname)
}
type sshHostnameOption string
func (o sshHostnameOption) applyGitProvider(b *GitProviderBootstrapper) {
b.sshHostname = string(o)
}
func (b *GitProviderBootstrapper) ReconcileSyncConfig(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error {
repo, err := b.getRepository(ctx)
if err != nil {
return err
}
if b.url == "" {
bootstrapURL, err := b.getCloneURL(repo, gitprovider.TransportType(b.bootstrapTransportType))
if err != nil {
return err
}
WithRepositoryURL(bootstrapURL).applyGit(b.PlainGitBootstrapper)
}
if options.URL == "" {
syncURL, err := b.getCloneURL(repo, gitprovider.TransportType(b.syncTransportType))
if err != nil {
return err
}
options.URL = syncURL
}
return b.PlainGitBootstrapper.ReconcileSyncConfig(ctx, options, pollInterval, timeout)
}
// ReconcileRepository reconciles an organization or user repository with the
// GitProviderBootstrapper configuration. On success, the URL in the embedded
// PlainGitBootstrapper is set to clone URL for the configured protocol.
//
// When part of the reconciliation fails with a warning without aborting, an
// ErrReconciledWithWarning error is returned.
func (b *GitProviderBootstrapper) ReconcileRepository(ctx context.Context) error {
var repo gitprovider.UserRepository
var err error
if b.personal {
repo, err = b.reconcileUserRepository(ctx)
} else {
repo, err = b.reconcileOrgRepository(ctx)
}
if err != nil && !errors.Is(err, ErrReconciledWithWarning) {
return err
}
cloneURL := repo.Repository().GetCloneURL(gitprovider.TransportType(b.bootstrapTransportType))
// TODO(hidde): https://github.com/fluxcd/go-git-providers/issues/55
if strings.HasPrefix(cloneURL, "https://https://") {
cloneURL = strings.TrimPrefix(cloneURL, "https://")
}
WithRepositoryURL(cloneURL).applyGit(b.PlainGitBootstrapper)
return err
}
func (b *GitProviderBootstrapper) reconcileDeployKey(ctx context.Context, secret corev1.Secret, options sourcesecret.Options) error {
ppk, ok := secret.StringData[sourcesecret.PublicKeySecretKey]
if !ok {
return nil
}
b.logger.Successf("public key: %s", strings.TrimSpace(ppk))
repo, err := b.getRepository(ctx)
if err != nil {
return err
}
name := deployKeyName(options.Namespace, b.branch, options.Name, options.TargetPath)
deployKeyInfo := newDeployKeyInfo(name, ppk, b.readWriteKey)
var changed bool
if _, changed, err = repo.DeployKeys().Reconcile(ctx, deployKeyInfo); err != nil {
return err
}
if changed {
b.logger.Successf("configured deploy key %q for %q", deployKeyInfo.Name, repo.Repository().String())
}
return nil
}
// reconcileOrgRepository reconciles a gitprovider.OrgRepository
// with the GitProviderBootstrapper values, including any
// gitprovider.TeamAccessInfo configurations.
//
// If one of the gitprovider.TeamAccessInfo does not reconcile
// successfully, the gitprovider.UserRepository and an
// ErrReconciledWithWarning error are returned.
func (b *GitProviderBootstrapper) reconcileOrgRepository(ctx context.Context) (gitprovider.UserRepository, error) {
b.logger.Actionf("connecting to %s", b.provider.SupportedDomain())
// Construct the repository and other configuration objects
// go-git-provider likes to work with
subOrgs, repoName := splitSubOrganizationsFromRepositoryName(b.repository)
orgRef := newOrganizationRef(b.provider.SupportedDomain(), b.owner, subOrgs)
repoRef := newOrgRepositoryRef(orgRef, repoName)
repoInfo := newRepositoryInfo(b.description, b.defaultBranch, b.visibility)
// Reconcile the organization repository
repo, err := b.provider.OrgRepositories().Get(ctx, repoRef)
if err != nil {
if !errors.Is(err, gitprovider.ErrNotFound) {
return nil, fmt.Errorf("failed to get Git repository %q: %w", repoRef.String(), err)
}
// go-git-providers has at present some issues with the idempotency
// of the available Reconcile methods, and setting e.g. the default
// branch correctly. Resort to Create with AutoInit until this has
// been resolved.
repo, err = b.provider.OrgRepositories().Create(ctx, repoRef, repoInfo, &gitprovider.RepositoryCreateOptions{
AutoInit: gitprovider.BoolVar(true),
})
if err != nil {
return nil, fmt.Errorf("failed to create new Git repository %q: %w", repoRef.String(), err)
}
b.logger.Successf("repository %q created", repoRef.String())
}
// Set default branch before calling Reconcile due to bug described
// above.
repoInfo.DefaultBranch = repo.Get().DefaultBranch
var changed bool
if err = retry(1, 2*time.Second, func() (err error) {
repo, changed, err = b.provider.OrgRepositories().Reconcile(ctx, repoRef, repoInfo)
return
}); err != nil {
return nil, fmt.Errorf("failed to reconcile Git repository %q: %w", repoRef.String(), err)
}
if changed {
b.logger.Successf("repository %q reconciled", repoRef.String())
}
// Build the team access config
teamAccessInfo, err := buildTeamAccessInfo(b.teams, gitprovider.RepositoryPermissionVar(gitprovider.RepositoryPermissionMaintain))
if err != nil {
return nil, fmt.Errorf("failed to reconcile repository team access: %w", err)
}
// Reconcile the team access config on best effort (that being:
// record the error as a warning, but continue with the
// reconciliation of the others)
var warning error
if count := len(teamAccessInfo); count > 0 {
b.logger.Actionf("reconciling repository permissions")
for _, i := range teamAccessInfo {
var err error
_, changed, err = repo.TeamAccess().Reconcile(ctx, i)
if err != nil {
warning = fmt.Errorf("failed to grant permissions to team: %w", ErrReconciledWithWarning)
b.logger.Failuref("failed to grant %q permissions to %q: %w", *i.Permission, i.Name, err)
}
if changed {
b.logger.Successf("granted %q permissions to %q", *i.Permission, i.Name)
}
}
b.logger.Successf("reconciled repository permissions")
}
return repo, warning
}
// reconcileUserRepository reconciles a gitprovider.UserRepository
// with the GitProviderBootstrapper values. It returns the reconciled
// gitprovider.UserRepository, or an error.
func (b *GitProviderBootstrapper) reconcileUserRepository(ctx context.Context) (gitprovider.UserRepository, error) {
b.logger.Actionf("connecting to %s", b.provider.SupportedDomain())
// Construct the repository and other metadata objects
// go-git-provider likes to work with.
_, repoName := splitSubOrganizationsFromRepositoryName(b.repository)
userRef := newUserRef(b.provider.SupportedDomain(), b.owner)
repoRef := newUserRepositoryRef(userRef, repoName)
repoInfo := newRepositoryInfo(b.description, b.defaultBranch, b.visibility)
repo, err := b.provider.UserRepositories().Get(ctx, repoRef)
if err != nil {
if !errors.Is(err, gitprovider.ErrNotFound) {
return nil, fmt.Errorf("failed to get Git repository %q: %w", repoRef.String(), err)
}
// go-git-providers has at present some issues with the idempotency
// of the available Reconcile methods, and setting e.g. the default
// branch correctly. Resort to Create with AutoInit until this has
// been resolved.
repo, err = b.provider.UserRepositories().Create(ctx, repoRef, repoInfo, &gitprovider.RepositoryCreateOptions{
AutoInit: gitprovider.BoolVar(true),
})
if err != nil {
return nil, fmt.Errorf("failed to create new Git repository %q: %w", repoRef.String(), err)
}
b.logger.Successf("repository %q created", repoRef.String())
}
// Set default branch before calling Reconcile due to bug described
// above.
repoInfo.DefaultBranch = repo.Get().DefaultBranch
var changed bool
if err = retry(1, 2*time.Second, func() (err error) {
repo, changed, err = b.provider.UserRepositories().Reconcile(ctx, repoRef, repoInfo)
return
}); err != nil {
return nil, fmt.Errorf("failed to reconcile Git repository %q: %w", repoRef.String(), err)
}
if changed {
b.logger.Successf("repository %q reconciled", repoRef.String())
}
return repo, nil
}
// getRepository retrieves and returns the gitprovider.UserRepository
// for organization and user repositories using the
// GitProviderBootstrapper values.
// As gitprovider.OrgRepository is a superset of gitprovider.UserRepository, this
// type is returned.
func (b *GitProviderBootstrapper) getRepository(ctx context.Context) (gitprovider.UserRepository, error) {
subOrgs, repoName := splitSubOrganizationsFromRepositoryName(b.repository)
if b.personal {
userRef := newUserRef(b.provider.SupportedDomain(), b.owner)
return b.provider.UserRepositories().Get(ctx, newUserRepositoryRef(userRef, repoName))
}
orgRef := newOrganizationRef(b.provider.SupportedDomain(), b.owner, subOrgs)
return b.provider.OrgRepositories().Get(ctx, newOrgRepositoryRef(orgRef, repoName))
}
// getCloneURL returns the Git clone URL for the given
// gitprovider.UserRepository. If the given transport type is
// gitprovider.TransportTypeSSH and a custom SSH hostname is configured,
// the hostname of the URL will be modified to this hostname.
func (b *GitProviderBootstrapper) getCloneURL(repository gitprovider.UserRepository, transport gitprovider.TransportType) (string, error) {
u := repository.Repository().GetCloneURL(transport)
// TODO(hidde): https://github.com/fluxcd/go-git-providers/issues/55
if strings.HasPrefix(u, "https://https://") {
u = strings.TrimPrefix(u, "https://")
}
var err error
if transport == gitprovider.TransportTypeSSH && b.sshHostname != "" {
if u, err = setHostname(u, b.sshHostname); err != nil {
err = fmt.Errorf("failed to set SSH hostname for URL %q: %w", u, err)
}
}
return u, err
}
// splitSubOrganizationsFromRepositoryName removes any prefixed sub
// organizations from the given repository name by splitting the
// string into a slice by '/'.
// The last (or only) item of the slice result is assumed to be the
// repository name, other items (nested) sub organizations.
func splitSubOrganizationsFromRepositoryName(name string) ([]string, string) {
elements := strings.Split(name, "/")
i := len(elements)
switch i {
case 1:
return nil, name
default:
return elements[:i-1], elements[i-1]
}
}
// buildTeamAccessInfo constructs a gitprovider.TeamAccessInfo slice
// from the given string map of team names to permissions.
//
// Providing a default gitprovider.RepositoryPermission is optional,
// and omitting it will make it default to the go-git-provider default.
//
// An error is returned if any of the given permissions is invalid.
func buildTeamAccessInfo(m map[string]string, defaultPermissions *gitprovider.RepositoryPermission) ([]gitprovider.TeamAccessInfo, error) {
var infos []gitprovider.TeamAccessInfo
if defaultPermissions != nil {
if err := gitprovider.ValidateRepositoryPermission(*defaultPermissions); err != nil {
return nil, fmt.Errorf("invalid default team permission %q", *defaultPermissions)
}
}
for n, p := range m {
permission := defaultPermissions
if p != "" {
p := gitprovider.RepositoryPermission(p)
if err := gitprovider.ValidateRepositoryPermission(p); err != nil {
return nil, fmt.Errorf("invalid permission %q for team %q", p, n)
}
permission = &p
}
i := gitprovider.TeamAccessInfo{
Name: n,
Permission: permission,
}
infos = append(infos, i)
}
return infos, nil
}
// newOrganizationRef constructs a gitprovider.OrganizationRef with the
// given values and returns the result.
func newOrganizationRef(domain, organization string, subOrganizations []string) gitprovider.OrganizationRef {
return gitprovider.OrganizationRef{
Domain: domain,
Organization: organization,
SubOrganizations: subOrganizations,
}
}
// newOrgRepositoryRef constructs a gitprovider.OrgRepositoryRef with
// the given values and returns the result.
func newOrgRepositoryRef(organizationRef gitprovider.OrganizationRef, name string) gitprovider.OrgRepositoryRef {
return gitprovider.OrgRepositoryRef{
OrganizationRef: organizationRef,
RepositoryName: name,
}
}
// newUserRef constructs a gitprovider.UserRef with the given values
// and returns the result.
func newUserRef(domain, login string) gitprovider.UserRef {
return gitprovider.UserRef{
Domain: domain,
UserLogin: login,
}
}
// newUserRepositoryRef constructs a gitprovider.UserRepositoryRef with
// the given values and returns the result.
func newUserRepositoryRef(userRef gitprovider.UserRef, name string) gitprovider.UserRepositoryRef {
return gitprovider.UserRepositoryRef{
UserRef: userRef,
RepositoryName: name,
}
}
// newRepositoryInfo constructs a gitprovider.RepositoryInfo with the
// given values and returns the result.
func newRepositoryInfo(description, defaultBranch, visibility string) gitprovider.RepositoryInfo {
var i gitprovider.RepositoryInfo
if description != "" {
i.Description = gitprovider.StringVar(description)
}
if defaultBranch != "" {
i.DefaultBranch = gitprovider.StringVar(defaultBranch)
}
if visibility != "" {
i.Visibility = gitprovider.RepositoryVisibilityVar(gitprovider.RepositoryVisibility(visibility))
}
return i
}
// newDeployKeyInfo constructs a gitprovider.DeployKeyInfo with the
// given values and returns the result.
func newDeployKeyInfo(name, publicKey string, readWrite bool) gitprovider.DeployKeyInfo {
keyInfo := gitprovider.DeployKeyInfo{
Name: name,
Key: []byte(publicKey),
}
if readWrite {
keyInfo.ReadOnly = gitprovider.BoolVar(false)
}
return keyInfo
}
func deployKeyName(namespace, secretName, branch, path string) string {
var name string
for _, v := range []string{namespace, secretName, branch, path} {
if v == "" {
continue
}
if name == "" {
name = v
} else {
name = name + "-" + v
}
}
return name
}
// setHostname is a helper to replace the hostname of the given URL.
// TODO(hidde): support for this should be added in go-git-providers.
func setHostname(URL, hostname string) (string, error) {
u, err := url.Parse(URL)
if err != nil {
return URL, err
}
u.Host = hostname
return u.String(), nil
}

View File

@@ -0,0 +1,51 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package git
import (
"context"
"errors"
"io"
)
var (
ErrNoGitRepository = errors.New("no git repository")
ErrNoStagedFiles = errors.New("no staged files")
)
type Author struct {
Name string
Email string
}
type Commit struct {
Author
Hash string
Message string
}
// Git is an interface for basic Git operations on a single branch of a
// remote repository.
type Git interface {
Init(url, branch string) (bool, error)
Clone(ctx context.Context, url, branch string) (bool, error)
Write(path string, reader io.Reader) error
Commit(message Commit) (string, error)
Push(ctx context.Context) error
Status() (bool, error)
Path() string
}

View File

@@ -0,0 +1,221 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gogit
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
gogit "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/fluxcd/flux2/internal/bootstrap/git"
)
type GoGit struct {
path string
auth transport.AuthMethod
repository *gogit.Repository
}
func New(path string, auth transport.AuthMethod) *GoGit {
return &GoGit{
path: path,
auth: auth,
}
}
func (g *GoGit) Init(url, branch string) (bool, error) {
if g.repository != nil {
return false, nil
}
r, err := gogit.PlainInit(g.path, false)
if err != nil {
return false, err
}
if _, err = r.CreateRemote(&config.RemoteConfig{
Name: gogit.DefaultRemoteName,
URLs: []string{url},
}); err != nil {
return false, err
}
branchRef := plumbing.NewBranchReferenceName(branch)
if err = r.CreateBranch(&config.Branch{
Name: branch,
Remote: gogit.DefaultRemoteName,
Merge: branchRef,
}); err != nil {
return false, err
}
// PlainInit assumes the initial branch to always be master, we can
// overwrite this by setting the reference of the Storer to a new
// symbolic reference (as there are no commits yet) that points
// the HEAD to our new branch.
if err = r.Storer.SetReference(plumbing.NewSymbolicReference(plumbing.HEAD, branchRef)); err != nil {
return false, err
}
g.repository = r
return true, nil
}
func (g *GoGit) Clone(ctx context.Context, url, branch string) (bool, error) {
branchRef := plumbing.NewBranchReferenceName(branch)
r, err := gogit.PlainCloneContext(ctx, g.path, false, &gogit.CloneOptions{
URL: url,
Auth: g.auth,
RemoteName: gogit.DefaultRemoteName,
ReferenceName: branchRef,
SingleBranch: true,
NoCheckout: false,
Progress: nil,
Tags: gogit.NoTags,
})
if err != nil {
if err == transport.ErrEmptyRemoteRepository || isRemoteBranchNotFoundErr(err, branchRef.String()) {
return g.Init(url, branch)
}
return false, err
}
g.repository = r
return true, nil
}
func (g *GoGit) Write(path string, reader io.Reader) error {
if g.repository == nil {
return git.ErrNoGitRepository
}
wt, err := g.repository.Worktree()
if err != nil {
return err
}
f, err := wt.Filesystem.Create(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, reader)
return err
}
func (g *GoGit) Commit(message git.Commit) (string, error) {
if g.repository == nil {
return "", git.ErrNoGitRepository
}
wt, err := g.repository.Worktree()
if err != nil {
return "", err
}
status, err := wt.Status()
if err != nil {
return "", err
}
// go-git has [a bug](https://github.com/go-git/go-git/issues/253)
// whereby it thinks broken symlinks to absolute paths are
// modified. There's no circumstance in which we want to commit a
// change to a broken symlink: so, detect and skip those.
var changed bool
for file, _ := range status {
abspath := filepath.Join(g.path, file)
info, err := os.Lstat(abspath)
if err != nil {
return "", fmt.Errorf("checking if %s is a symlink: %w", file, err)
}
if info.Mode()&os.ModeSymlink > 0 {
// symlinks are OK; broken symlinks are probably a result
// of the bug mentioned above, but not of interest in any
// case.
if _, err := os.Stat(abspath); os.IsNotExist(err) {
continue
}
}
_, _ = wt.Add(file)
changed = true
}
if !changed {
head, err := g.repository.Head()
if err != nil {
return "", err
}
return head.Hash().String(), git.ErrNoStagedFiles
}
commit, err := wt.Commit(message.Message, &gogit.CommitOptions{
Author: &object.Signature{
Name: message.Name,
Email: message.Email,
When: time.Now(),
},
})
if err != nil {
return "", err
}
return commit.String(), nil
}
func (g *GoGit) Push(ctx context.Context) error {
if g.repository == nil {
return git.ErrNoGitRepository
}
return g.repository.PushContext(ctx, &gogit.PushOptions{
RemoteName: gogit.DefaultRemoteName,
Auth: g.auth,
Progress: nil,
})
}
func (g *GoGit) Status() (bool, error) {
if g.repository == nil {
return false, git.ErrNoGitRepository
}
wt, err := g.repository.Worktree()
if err != nil {
return false, err
}
status, err := wt.Status()
if err != nil {
return false, err
}
return status.IsClean(), nil
}
func (g *GoGit) Path() string {
return g.path
}
func isRemoteBranchNotFoundErr(err error, ref string) bool {
return strings.Contains(err.Error(), fmt.Sprintf("couldn't find remote ref %q", ref))
}

View File

@@ -0,0 +1,114 @@
/*
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 bootstrap
import (
"github.com/fluxcd/flux2/internal/bootstrap/git"
"github.com/fluxcd/flux2/pkg/log"
)
type Option interface {
GitOption
GitProviderOption
}
func WithBranch(branch string) Option {
return branchOption(branch)
}
type branchOption string
func (o branchOption) applyGit(b *PlainGitBootstrapper) {
b.branch = string(o)
}
func (o branchOption) applyGitProvider(b *GitProviderBootstrapper) {
o.applyGit(b.PlainGitBootstrapper)
}
func WithAuthor(name, email string) Option {
return authorOption{
Name: name,
Email: email,
}
}
type authorOption git.Author
func (o authorOption) applyGit(b *PlainGitBootstrapper) {
if o.Name != "" {
b.author.Name = o.Name
}
if o.Email != "" {
b.author.Email = o.Email
}
}
func (o authorOption) applyGitProvider(b *GitProviderBootstrapper) {
o.applyGit(b.PlainGitBootstrapper)
}
func WithCommitMessageAppendix(appendix string) Option {
return commitMessageAppendixOption(appendix)
}
type commitMessageAppendixOption string
func (o commitMessageAppendixOption) applyGit(b *PlainGitBootstrapper) {
b.commitMessageAppendix = string(o)
}
func (o commitMessageAppendixOption) applyGitProvider(b *GitProviderBootstrapper) {
o.applyGit(b.PlainGitBootstrapper)
}
func WithKubeconfig(kubeconfig, kubecontext string) Option {
return kubeconfigOption{
kubeconfig: kubeconfig,
kubecontext: kubecontext,
}
}
type kubeconfigOption struct {
kubeconfig string
kubecontext string
}
func (o kubeconfigOption) applyGit(b *PlainGitBootstrapper) {
b.kubeconfig = o.kubeconfig
b.kubecontext = o.kubecontext
}
func (o kubeconfigOption) applyGitProvider(b *GitProviderBootstrapper) {
o.applyGit(b.PlainGitBootstrapper)
}
func WithLogger(logger log.Logger) Option {
return loggerOption{logger}
}
type loggerOption struct {
logger log.Logger
}
func (o loggerOption) applyGit(b *PlainGitBootstrapper) {
b.logger = o.logger
}
func (o loggerOption) applyGitProvider(b *GitProviderBootstrapper) {
b.logger = o.logger
}

View File

@@ -0,0 +1,58 @@
/*
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 provider
import (
"fmt"
"github.com/fluxcd/go-git-providers/github"
"github.com/fluxcd/go-git-providers/gitlab"
"github.com/fluxcd/go-git-providers/gitprovider"
)
// BuildGitProvider builds a gitprovider.Client for the provided
// Config. It returns an error if the Config.Provider
// is not supported, or if the construction of the client fails.
func BuildGitProvider(config Config) (gitprovider.Client, error) {
var client gitprovider.Client
var err error
switch config.Provider {
case GitProviderGitHub:
opts := []github.ClientOption{
github.WithOAuth2Token(config.Token),
}
if config.Hostname != "" {
opts = append(opts, github.WithDomain(config.Hostname))
}
if client, err = github.NewClient(opts...); err != nil {
return nil, err
}
case GitProviderGitLab:
opts := []gitlab.ClientOption{
gitlab.WithConditionalRequests(true),
}
if config.Hostname != "" {
opts = append(opts, gitlab.WithDomain(config.Hostname))
}
if client, err = gitlab.NewClient(config.Token, "", opts...); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported Git provider '%s'", config.Provider)
}
return client, err
}

View File

@@ -0,0 +1,39 @@
/*
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 provider
// GitProvider holds a Git provider definition.
type GitProvider string
const (
GitProviderGitHub GitProvider = "github"
GitProviderGitLab GitProvider = "gitlab"
)
// Config defines the configuration for connecting to a GitProvider.
type Config struct {
// Provider defines the GitProvider.
Provider GitProvider
// Hostname is the HTTP/S hostname of the Provider,
// e.g. github.example.com.
Hostname string
// Token contains the token used to authenticate with the
// Provider.
Token string
}

View File

@@ -28,8 +28,9 @@ import (
var supportedHelmChartSourceKinds = []string{sourcev1.HelmRepositoryKind, sourcev1.GitRepositoryKind, sourcev1.BucketKind} var supportedHelmChartSourceKinds = []string{sourcev1.HelmRepositoryKind, sourcev1.GitRepositoryKind, sourcev1.BucketKind}
type HelmChartSource struct { type HelmChartSource struct {
Kind string Kind string
Name string Name string
Namespace string
} }
func (s *HelmChartSource) String() string { func (s *HelmChartSource) String() string {
@@ -45,7 +46,7 @@ func (s *HelmChartSource) Set(str string) error {
s.Description()) s.Description())
} }
sourceKind, sourceName := utils.ParseObjectKindName(str) sourceKind, sourceName, sourceNamespace := utils.ParseObjectKindNameNamespace(str)
if sourceKind == "" || sourceName == "" { if sourceKind == "" || sourceName == "" {
return fmt.Errorf("invalid helm chart source '%s', must be in format <kind>/<name>", str) return fmt.Errorf("invalid helm chart source '%s', must be in format <kind>/<name>", str)
} }
@@ -55,8 +56,9 @@ func (s *HelmChartSource) Set(str string) error {
sourceKind, strings.Join(supportedHelmChartSourceKinds, ", ")) sourceKind, strings.Join(supportedHelmChartSourceKinds, ", "))
} }
s.Name = sourceName
s.Kind = cleanSourceKind s.Kind = cleanSourceKind
s.Name = sourceName
s.Namespace = sourceNamespace
return nil return nil
} }
@@ -67,7 +69,7 @@ func (s *HelmChartSource) Type() string {
func (s *HelmChartSource) Description() string { func (s *HelmChartSource) Description() string {
return fmt.Sprintf( return fmt.Sprintf(
"source that contains the chart in the format '<kind>/<name>', "+ "source that contains the chart in the format '<kind>/<name>.<namespace>', "+
"where kind must be one of: (%s)", "where kind must be one of: (%s)",
strings.Join(supportedHelmChartSourceKinds, ", "), strings.Join(supportedHelmChartSourceKinds, ", "),
) )

View File

@@ -28,8 +28,9 @@ import (
var supportedKustomizationSourceKinds = []string{sourcev1.GitRepositoryKind, sourcev1.BucketKind} var supportedKustomizationSourceKinds = []string{sourcev1.GitRepositoryKind, sourcev1.BucketKind}
type KustomizationSource struct { type KustomizationSource struct {
Kind string Kind string
Name string Name string
Namespace string
} }
func (s *KustomizationSource) String() string { func (s *KustomizationSource) String() string {
@@ -45,7 +46,7 @@ func (s *KustomizationSource) Set(str string) error {
s.Description()) s.Description())
} }
sourceKind, sourceName := utils.ParseObjectKindName(str) sourceKind, sourceName, sourceNamespace := utils.ParseObjectKindNameNamespace(str)
if sourceName == "" { if sourceName == "" {
return fmt.Errorf("no name given for source of kind '%s'", sourceKind) return fmt.Errorf("no name given for source of kind '%s'", sourceKind)
} }
@@ -61,8 +62,9 @@ func (s *KustomizationSource) Set(str string) error {
sourceKind, strings.Join(supportedKustomizationSourceKinds, ", ")) sourceKind, strings.Join(supportedKustomizationSourceKinds, ", "))
} }
s.Name = sourceName
s.Kind = cleanSourceKind s.Kind = cleanSourceKind
s.Name = sourceName
s.Namespace = sourceNamespace
return nil return nil
} }
@@ -73,7 +75,7 @@ func (s *KustomizationSource) Type() string {
func (s *KustomizationSource) Description() string { func (s *KustomizationSource) Description() string {
return fmt.Sprintf( return fmt.Sprintf(
"source that contains the Kubernetes manifests in the format '[<kind>/]<name>', "+ "source that contains the Kubernetes manifests in the format '[<kind>/]<name>.<namespace>', "+
"where kind must be one of: (%s), if kind is not specified it defaults to GitRepository", "where kind must be one of: (%s), if kind is not specified it defaults to GitRepository",
strings.Join(supportedKustomizationSourceKinds, ", "), strings.Join(supportedKustomizationSourceKinds, ", "),
) )

View File

@@ -216,9 +216,10 @@ func ContainsEqualFoldItemString(s []string, e string) (string, bool) {
return "", false return "", false
} }
func ParseObjectKindName(input string) (string, string) { // ParseObjectKindName extracts the kind and name of a resource
kind := "" // based on the '<kind>/<name>' format
name := input func ParseObjectKindName(input string) (kind, name string) {
name = input
parts := strings.Split(input, "/") parts := strings.Split(input, "/")
if len(parts) == 2 { if len(parts) == 2 {
kind, name = parts[0], parts[1] kind, name = parts[0], parts[1]
@@ -226,6 +227,23 @@ func ParseObjectKindName(input string) (string, string) {
return kind, name return kind, name
} }
// ParseObjectKindNameNamespace extracts the kind, name and namespace of a resource
// based on the '<kind>/<name>.<namespace>' format
func ParseObjectKindNameNamespace(input string) (kind, name, namespace string) {
name = input
parts := strings.Split(input, "/")
if len(parts) == 2 {
kind, name = parts[0], parts[1]
}
if nn := strings.Split(name, "."); len(nn) > 1 {
name = strings.Join(nn[:len(nn)-1], ".")
namespace = nn[len(nn)-1]
}
return kind, name, namespace
}
func MakeDependsOn(deps []string) []dependency.CrossNamespaceDependencyReference { func MakeDependsOn(deps []string) []dependency.CrossNamespaceDependencyReference {
refs := []dependency.CrossNamespaceDependencyReference{} refs := []dependency.CrossNamespaceDependencyReference{}
for _, dep := range deps { for _, dep := range deps {

View File

@@ -1,8 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
resources: resources:
- https://github.com/fluxcd/image-automation-controller/releases/download/v0.7.0/image-automation-controller.crds.yaml - https://github.com/fluxcd/image-automation-controller/releases/download/v0.8.0/image-automation-controller.crds.yaml
- https://github.com/fluxcd/image-automation-controller/releases/download/v0.7.0/image-automation-controller.deployment.yaml - https://github.com/fluxcd/image-automation-controller/releases/download/v0.8.0/image-automation-controller.deployment.yaml
- account.yaml - account.yaml
patchesJson6902: patchesJson6902:
- target: - target:

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