Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bce0da2806 | ||
|
|
a58c40f2d7 | ||
|
|
65d5cadf29 | ||
|
|
1ea5d4d2e3 | ||
|
|
719ef3c44c | ||
|
|
f4adfc3029 | ||
|
|
d8d08091cc | ||
|
|
de4b3ef3dc | ||
|
|
7bd6aedb73 | ||
|
|
fffe40fbd4 | ||
|
|
74feda73af | ||
|
|
8b5583930e | ||
|
|
466fdae70e | ||
|
|
054a62fb30 | ||
|
|
c694b570e0 | ||
|
|
4204ec1d43 | ||
|
|
6d4e37ccb2 | ||
|
|
eef06c993e | ||
|
|
58362fbbb9 | ||
|
|
b872e595ae | ||
|
|
18c3f79319 | ||
|
|
8f0cd35d7a | ||
|
|
c8bcf19f32 | ||
|
|
5bee3047ac | ||
|
|
0d2f6bf02d | ||
|
|
7481c6beb0 | ||
|
|
4ece12348b | ||
|
|
e65a5beaae | ||
|
|
ef576128e3 | ||
|
|
7f0bc2ada2 | ||
|
|
96c373d045 | ||
|
|
22648cae3b | ||
|
|
f57ce14754 | ||
|
|
1d3a381389 | ||
|
|
9055e753a9 | ||
|
|
6390812cbb | ||
|
|
fa46f05423 | ||
|
|
6b0ffe0b13 | ||
|
|
e724d90202 | ||
|
|
6129943685 | ||
|
|
d4e37cbda5 | ||
|
|
cccfb3a560 | ||
|
|
d0403038ed | ||
|
|
a5a7d7970f | ||
|
|
62b9377f15 | ||
|
|
ec2c71f9ef | ||
|
|
b54fd2c6b3 | ||
|
|
e5066c3712 | ||
|
|
fd1c038303 |
9
.github/workflows/bootstrap.yaml
vendored
9
.github/workflows/bootstrap.yaml
vendored
@@ -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
|
||||||
|
|||||||
5
.github/workflows/e2e.yaml
vendored
5
.github/workflows/e2e.yaml
vendored
@@ -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
|
||||||
|
|||||||
20
.github/workflows/release.yaml
vendored
20
.github/workflows/release.yaml
vendored
@@ -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
3
.gitignore
vendored
@@ -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/
|
||||||
|
|||||||
@@ -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
23
Dockerfile
Normal 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" ]
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# Flux version 2
|
# Flux version 2
|
||||||
|
|
||||||
|
[](https://bestpractices.coreinfrastructure.org/projects/4782)
|
||||||
[](https://github.com/fluxcd/flux2/actions)
|
[](https://github.com/fluxcd/flux2/actions)
|
||||||
[](https://goreportcard.com/report/github.com/fluxcd/flux2)
|
[](https://goreportcard.com/report/github.com/fluxcd/flux2)
|
||||||
[](https://github.com/fluxcd/flux2/blob/main/LICENSE)
|
[](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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
256
cmd/flux/bootstrap_git.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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...))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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{¬ificationv1.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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
91
cmd/flux/reconcile_with_source.go
Normal file
91
cmd/flux/reconcile_with_source.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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{¬ificationv1.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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{¬ificationv1.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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{¬ificationv1.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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{¬ificationv1.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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
80
docs/cmd/flux_bootstrap_git.md
Normal file
80
docs/cmd/flux_bootstrap_git.md
Normal 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
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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'
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
14
go.mod
@@ -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
40
go.sum
@@ -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=
|
||||||
|
|||||||
198
internal/bootstrap/bootstrap.go
Normal file
198
internal/bootstrap/bootstrap.go
Normal 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
|
||||||
|
}
|
||||||
330
internal/bootstrap/bootstrap_plain_git.go
Normal file
330
internal/bootstrap/bootstrap_plain_git.go
Normal 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
|
||||||
|
}
|
||||||
546
internal/bootstrap/bootstrap_provider.go
Normal file
546
internal/bootstrap/bootstrap_provider.go
Normal 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
|
||||||
|
}
|
||||||
51
internal/bootstrap/git/git.go
Normal file
51
internal/bootstrap/git/git.go
Normal 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
|
||||||
|
}
|
||||||
221
internal/bootstrap/git/gogit/gogit.go
Normal file
221
internal/bootstrap/git/gogit/gogit.go
Normal 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))
|
||||||
|
}
|
||||||
114
internal/bootstrap/options.go
Normal file
114
internal/bootstrap/options.go
Normal 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
|
||||||
|
}
|
||||||
58
internal/bootstrap/provider/factory.go
Normal file
58
internal/bootstrap/provider/factory.go
Normal 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
|
||||||
|
}
|
||||||
39
internal/bootstrap/provider/provider.go
Normal file
39
internal/bootstrap/provider/provider.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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, ", "),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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, ", "),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user