Merge branch 'main' into let-user-set-version-to-install

pull/1847/head
Jakub Baron 3 years ago committed by GitHub
commit 641d5378f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -8,7 +8,6 @@ pkgbase = flux-bin
arch = armv7h
arch = aarch64
license = APACHE
optdepends = kubectl
source_x86_64 = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_amd64.tar.gz
source_armv6h = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm.tar.gz
source_armv7h = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm.tar.gz

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

@ -10,7 +10,6 @@ pkgbase = flux-go
license = APACHE
makedepends = go
depends = glibc
optdepends = kubectl
provides = flux-bin
conflicts = flux-bin
replaces = flux-cli

@ -13,8 +13,7 @@ conflicts=("flux-bin")
replaces=("flux-cli")
depends=("glibc")
makedepends=('go>=1.16', 'kustomize>=3.0')
optdepends=('kubectl: for apply actions on the Kubernetes cluster',
'bash-completion: auto-completion for flux in Bash',
optdepends=('bash-completion: auto-completion for flux in Bash',
'zsh-completions: auto-completion for flux in ZSH')
source=(
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/archive/v${pkgver}.tar.gz"

@ -10,7 +10,6 @@ pkgbase = flux-scm
license = APACHE
makedepends = go
depends = glibc
optdepends = kubectl
provides = flux-bin
conflicts = flux-bin
source = git+https://github.com/fluxcd/flux2.git

@ -12,8 +12,7 @@ provides=("flux-bin")
conflicts=("flux-bin")
depends=("glibc")
makedepends=('go>=1.16', 'kustomize>=3.0')
optdepends=('kubectl: for apply actions on the Kubernetes cluster',
'bash-completion: auto-completion for flux in Bash',
optdepends=('bash-completion: auto-completion for flux in Bash',
'zsh-completions: auto-completion for flux in ZSH')
source=(
"git+https://github.com/fluxcd/flux2.git"

@ -0,0 +1,66 @@
name: e2e-azure
on:
workflow_dispatch:
schedule:
- cron: '0 6 * * *'
push:
branches: [ azure* ]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Restore Go cache
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go1.16-
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.16.x
- name: Install libgit2
run: |
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 648ACFD622F3D138
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 0E98404D386FA1D9
echo "deb http://deb.debian.org/debian unstable main" | sudo tee -a /etc/apt/sources.list
echo "deb-src http://deb.debian.org/debian unstable main" | sudo tee -a /etc/apt/sources.list
sudo apt-get update
sudo apt-get install -y --allow-downgrades libgit2-dev/unstable zlib1g-dev/unstable libssh2-1-dev/unstable libpcre3-dev/unstable
- name: Setup Flux CLI
run: |
make build
mkdir -p $HOME/.local/bin
mv ./bin/flux $HOME/.local/bin
- name: Setup SOPS
run: |
wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux
chmod +x sops-v3.7.1.linux
mkdir -p $HOME/.local/bin
mv sops-v3.7.1.linux $HOME/.local/bin/sops
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
with:
terraform_version: 1.0.7
terraform_wrapper: false
- name: Setup Azure CLI
run: |
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
- name: Run Azure e2e tests
env:
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
run: |
echo $HOME
echo $PATH
ls $HOME/.local/bin
az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID}
cd ./tests/azure
go test -v -coverprofile cover.out -timeout 60m .

@ -27,7 +27,7 @@ jobs:
uses: engineerd/setup-kind@v0.5.0
with:
version: v0.11.1
image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6
image: kindest/node:v1.19.11@sha256:07db187ae84b4b7de440a73886f008cf903fcf5764ba8106a9fd5243d6f32729
config: .github/kind/config.yaml # disable KIND-net
- name: Setup envtest
uses: fluxcd/pkg/actions/envtest@main
@ -93,7 +93,6 @@ jobs:
--path="./deploy/overlays/dev" \
--prune=true \
--interval=5m \
--validation=client \
--health-check="Deployment/frontend.dev" \
--health-check="Deployment/backend.dev" \
--health-check-timeout=3m

@ -49,9 +49,17 @@ brews:
folder: Formula
homepage: "https://fluxcd.io/"
description: "Flux CLI"
dependencies:
- name: kubectl
type: optional
install: |
bin.install "flux"
bash_output = Utils.safe_popen_read(bin/"flux", "completion", "bash")
(bash_completion/"flux").write bash_output
zsh_output = Utils.safe_popen_read(bin/"flux", "completion", "zsh")
(zsh_completion/"_flux").write zsh_output
fish_output = Utils.safe_popen_read(bin/"flux", "completion", "fish")
(fish_completion/"flux.fish").write fish_output
test: |
system "#{bin}/flux --version"
publishers:

@ -3,7 +3,7 @@ FROM alpine:3.13 as builder
RUN apk add --no-cache ca-certificates curl
ARG ARCH=linux/amd64
ARG KUBECTL_VER=1.20.4
ARG KUBECTL_VER=1.22.2
RUN curl -sL https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/${ARCH}/kubectl \
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && \

@ -20,6 +20,7 @@ import (
"crypto/elliptic"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
@ -67,6 +68,10 @@ type bootstrapFlags struct {
authorName string
authorEmail string
gpgKeyRingPath string
gpgPassphrase string
gpgKeyID string
commitMessageAppendix string
}
@ -118,6 +123,10 @@ func init() {
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.gpgKeyRingPath, "gpg-key-ring", "", "path to GPG key ring for signing commits")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgPassphrase, "gpg-passphrase", "", "passphrase for decrypting GPG private key")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgKeyID, "gpg-key-id", "", "key id for selecting a particular key")
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())
@ -174,6 +183,10 @@ func mapTeamSlice(s []string, defaultPermission string) map[string]string {
m := make(map[string]string, len(s))
for _, v := range s {
m[v] = defaultPermission
if s := strings.Split(v, ":"); len(s) == 2 {
m[s[0]] = s[1]
}
}
return m
}

@ -171,8 +171,14 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
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)
// Configure repository URL to match auth config for sync
// Override existing user when user is not already set
// or when a username was passed in
if repositoryURL.User == nil || gitArgs.username != "git" {
repositoryURL.User = url.User(gitArgs.username)
}
repositoryURL.Scheme = "ssh"
if bootstrapArgs.sshHostname != "" {
repositoryURL.Host = bootstrapArgs.sshHostname
@ -218,6 +224,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
bootstrap.WithLogger(logger),
bootstrap.WithCABundle(caBundle),
bootstrap.WithGitCommitSigning(bootstrapArgs.gpgKeyRingPath, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
}
// Setup bootstrapper with constructed configs

@ -52,6 +52,9 @@ the bootstrap command will perform an upgrade if needed.`,
# Run bootstrap for a private repository and assign organization teams to it
flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug>
# Run bootstrap for a private repository and assign organization teams with their access level(e.g maintain, admin) to it
flux bootstrap github --owner=<organization> --repository=<repository name> --team=<team1 slug>:<access-level>
# Run bootstrap for a repository path
flux bootstrap github --owner=<organization> --repository=<repository name> --path=dev-cluster
@ -93,7 +96,7 @@ var githubArgs githubFlags
func init() {
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.owner, "owner", "", "GitHub user or organization name")
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.repository, "repository", "", "GitHub repository name")
bootstrapGitHubCmd.Flags().StringSliceVar(&githubArgs.teams, "team", []string{}, "GitHub team to be given maintainer access (also accepts comma-separated values)")
bootstrapGitHubCmd.Flags().StringSliceVar(&githubArgs.teams, "team", []string{}, "GitHub team and the access to be given to it(team:maintain). Defaults to maintainer access if no access level is specified (also accepts comma-separated values)")
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 setup or configured as private")
bootstrapGitHubCmd.Flags().DurationVar(&githubArgs.interval, "interval", time.Minute, "sync interval")

@ -18,15 +18,12 @@ package main
import (
"context"
"encoding/json"
"os"
"os/exec"
"time"
"github.com/Masterminds/semver/v3"
"github.com/spf13/cobra"
v1 "k8s.io/api/apps/v1"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
@ -56,8 +53,11 @@ type checkFlags struct {
extraComponents []string
}
type kubectlVersion struct {
ClientVersion *apimachineryversion.Info `json:"clientVersion"`
var kubernetesConstraints = []string{
">=1.19.0-0",
">=1.16.11-0 <=1.16.15-0",
">=1.17.7-0 <=1.17.17-0",
">=1.18.4-0 <=1.18.20-0",
}
var checkArgs checkFlags
@ -73,19 +73,12 @@ func init() {
}
func runCheckCmd(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
logger.Actionf("checking prerequisites")
checkFailed := false
fluxCheck()
if !kubectlCheck(ctx, ">=1.18.0-0") {
checkFailed = true
}
if !kubernetesCheck(">=1.16.0-0") {
if !kubernetesCheck(kubernetesConstraints) {
checkFailed = true
}
@ -130,43 +123,7 @@ func fluxCheck() {
}
}
func kubectlCheck(ctx context.Context, constraint string) bool {
_, err := exec.LookPath("kubectl")
if err != nil {
logger.Failuref("kubectl not found")
return false
}
kubectlArgs := []string{"version", "--client", "--output", "json"}
output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
if err != nil {
logger.Failuref("kubectl version can't be determined")
return false
}
kv := &kubectlVersion{}
if err = json.Unmarshal([]byte(output), kv); err != nil {
logger.Failuref("kubectl version output can't be unmarshalled")
return false
}
v, err := version.ParseVersion(kv.ClientVersion.GitVersion)
if err != nil {
logger.Failuref("kubectl version can't be parsed")
return false
}
c, _ := semver.NewConstraint(constraint)
if !c.Check(v) {
logger.Failuref("kubectl version %s < %s", v.Original(), constraint)
return false
}
logger.Successf("kubectl %s %s", v.String(), constraint)
return true
}
func kubernetesCheck(constraint string) bool {
func kubernetesCheck(constraints []string) bool {
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
@ -191,13 +148,23 @@ func kubernetesCheck(constraint string) bool {
return false
}
c, _ := semver.NewConstraint(constraint)
if !c.Check(v) {
logger.Failuref("Kubernetes version %s < %s", v.Original(), constraint)
var valid bool
var vrange string
for _, constraint := range constraints {
c, _ := semver.NewConstraint(constraint)
if c.Check(v) {
valid = true
vrange = constraint
break
}
}
if !valid {
logger.Failuref("Kubernetes version %s does not match %s", v.Original(), constraints[0])
return false
}
logger.Successf("Kubernetes %s %s", v.String(), constraint)
logger.Successf("Kubernetes %s %s", v.String(), vrange)
return true
}

@ -23,13 +23,11 @@ func TestCheckPre(t *testing.T) {
t.Fatalf("Error unmarshalling: %v", err.Error())
}
clientVersion := strings.TrimPrefix(versions["clientVersion"].GitVersion, "v")
serverVersion := strings.TrimPrefix(versions["serverVersion"].GitVersion, "v")
cmd := cmdTestCase{
args: "check --pre",
assert: assertGoldenTemplateFile("testdata/check/check_pre.golden", map[string]string{
"clientVersion": clientVersion,
"serverVersion": serverVersion,
}),
}

@ -187,9 +187,11 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}
if helmReleaseArgs.createNamespace {
helmRelease.Spec.Install = &helmv2.Install{
CreateNamespace: helmReleaseArgs.createNamespace,
if helmRelease.Spec.Install == nil {
helmRelease.Spec.Install = &helmv2.Install{}
}
helmRelease.Spec.Install.CreateNamespace = helmReleaseArgs.createNamespace
}
if helmReleaseArgs.saName != "" {
@ -197,6 +199,10 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}
if helmReleaseArgs.crds != "" {
if helmRelease.Spec.Install == nil {
helmRelease.Spec.Install = &helmv2.Install{}
}
helmRelease.Spec.Install.CRDs = helmv2.Create
helmRelease.Spec.Upgrade = &helmv2.Upgrade{CRDs: helmv2.CRDsPolicy(helmReleaseArgs.crds.String())}
}

@ -105,7 +105,7 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
Labels: labels,
},
Spec: imagev1.ImagePolicySpec{
ImageRepositoryRef: meta.LocalObjectReference{
ImageRepositoryRef: meta.NamespacedObjectReference{
Name: imagePolicyArgs.imageRef,
},
},

@ -31,7 +31,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/flags"
@ -49,7 +49,6 @@ var createKsCmd = &cobra.Command{
--path="./examples/contour/" \
--prune=true \
--interval=10m \
--validation=client \
--health-check="Deployment/contour.projectcontour" \
--health-check="DaemonSet/envoy.projectcontour" \
--health-check-timeout=3m
@ -60,8 +59,7 @@ var createKsCmd = &cobra.Command{
--source=GitRepository/webapp \
--path="./deploy/overlays/dev" \
--prune=true \
--interval=5m \
--validation=client
--interval=5m
# Create a Kustomization using a source from a different namespace
flux create kustomization podinfo \
@ -69,8 +67,7 @@ var createKsCmd = &cobra.Command{
--source=GitRepository/podinfo.flux-system \
--path="./deploy/overlays/dev" \
--prune=true \
--interval=5m \
--validation=client
--interval=5m
# Create a Kustomization resource that references a Bucket
flux create kustomization secrets \
@ -108,6 +105,8 @@ func init() {
createKsCmd.Flags().Var(&kustomizationArgs.decryptionProvider, "decryption-provider", kustomizationArgs.decryptionProvider.Description())
createKsCmd.Flags().StringVar(&kustomizationArgs.decryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption")
createKsCmd.Flags().StringVar(&kustomizationArgs.targetNamespace, "target-namespace", "", "overrides the namespace of all Kustomization objects reconciled by this Kustomization")
createKsCmd.Flags().MarkDeprecated("validation", "this arg is no longer used, all resources are validated using server-side apply dry-run")
createCmd.AddCommand(createKsCmd)
}
@ -158,7 +157,6 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
Namespace: kustomizationArgs.source.Namespace,
},
Suspend: false,
Validation: kustomizationArgs.validation,
TargetNamespace: kustomizationArgs.targetNamespace,
},
}

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

@ -20,7 +20,7 @@ import (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
)
var exportKsCmd = &cobra.Command{

@ -177,7 +177,7 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error {
return err
}
utils.PrintTable(cmd.OutOrStderr(), header, rows)
utils.PrintTable(cmd.OutOrStdout(), header, rows)
if getAll {
fmt.Println()

@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
)

@ -24,7 +24,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
)
var getKsCmd = &cobra.Command{

@ -41,13 +41,13 @@ If a previous version is installed, then an in-place upgrade will be performed.`
flux install --version=latest --namespace=flux-system
# Install a specific version and a series of components
flux install --dry-run --version=v0.0.7 --components="source-controller,kustomize-controller"
flux install --version=v0.0.7 --components="source-controller,kustomize-controller"
# Install Flux onto tainted Kubernetes nodes
flux install --toleration-keys=node.kubernetes.io/dedicated-to-flux
# Dry-run install with manifests preview
flux install --dry-run --verbose
# Dry-run install
flux install --export | kubectl apply --dry-run=client -f-
# Write install manifests to file
flux install --export > flux-system.yaml`,
@ -102,6 +102,7 @@ func init() {
"list of toleration keys used to schedule the components pods onto nodes with matching taints")
installCmd.Flags().MarkHidden("manifests")
installCmd.Flags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64")
installCmd.Flags().MarkDeprecated("dry-run", "use 'flux install --export | kubectl apply --dry-run=client -f-'")
rootCmd.AddCommand(installCmd)
}
@ -188,24 +189,18 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("manifests build completed")
logger.Actionf("installing components in %s namespace", rootArgs.namespace)
applyOutput := utils.ModeStderrOS
if rootArgs.verbose {
applyOutput = utils.ModeOS
}
kubectlArgs := []string{"apply", "-f", filepath.Join(tmpDir, manifest.Path)}
if installArgs.dryRun {
kubectlArgs = append(kubectlArgs, "--dry-run=client")
applyOutput = utils.ModeOS
logger.Successf("install dry-run finished")
return nil
}
if _, err := utils.ExecKubectlCommand(ctx, applyOutput, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
applyOutput, err := utils.Apply(ctx, rootArgs.kubeconfig, rootArgs.kubecontext, filepath.Join(tmpDir, manifest.Path))
if err != nil {
return fmt.Errorf("install failed: %w", err)
}
if installArgs.dryRun {
logger.Successf("install dry-run finished")
return nil
}
fmt.Fprintln(os.Stderr, applyOutput)
kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {

@ -19,7 +19,7 @@ package main
import (
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
)
// kustomizev1.Kustomization

@ -14,7 +14,7 @@ func TestKustomizationFromGit(t *testing.T) {
"testdata/kustomization/create_source_git.golden",
},
{
"create kustomization tkfg --source=tkfg --path=./deploy/overlays/dev --prune=true --interval=5m --validation=client --health-check=Deployment/frontend.dev --health-check=Deployment/backend.dev --health-check-timeout=3m",
"create kustomization tkfg --source=tkfg --path=./deploy/overlays/dev --prune=true --interval=5m --health-check=Deployment/frontend.dev --health-check=Deployment/backend.dev --health-check-timeout=3m",
"testdata/kustomization/create_kustomization_from_git.golden",
},
{

@ -24,6 +24,7 @@ import (
"html/template"
"io"
"os"
"sort"
"strings"
"sync"
"time"
@ -34,6 +35,7 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/podutils"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
@ -174,7 +176,17 @@ func getPods(ctx context.Context, c *kubernetes.Clientset, label string) ([]core
if err != nil {
return ret, err
}
ret = append(ret, podList.Items...)
pods := []*corev1.Pod{}
for i := range podList.Items {
pod := podList.Items[i]
pods = append(pods, &pod)
}
if len(pods) > 0 {
// sort pods to prioritize running pods over others
sort.Sort(podutils.ByLogging(pods))
ret = append(ret, *pods[0])
}
}
return ret, nil

@ -43,7 +43,7 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`,
flux check --pre
# Install the latest version of Flux
flux install --version=master
flux install
# Create a source for a public Git repository
flux create source git webapp-latest \
@ -66,7 +66,6 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.`,
--path="./deploy/webapp/" \
--prune=true \
--interval=5m \
--validation=client \
--health-check="Deployment/backend.webapp" \
--health-check="Deployment/frontend.webapp" \
--health-check-timeout=2m

@ -116,11 +116,13 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
return err
}
readyCond := apimeta.FindStatusCondition(*reconcile.object.GetStatusConditions(), meta.ReadyCondition)
if readyCond == nil {
return fmt.Errorf("status can't be determined")
}
logger.Successf("%s reconciliation completed", reconcile.kind)
if apimeta.IsStatusConditionFalse(*reconcile.object.GetStatusConditions(), meta.ReadyCondition) {
return fmt.Errorf("%s reconciliation failed", reconcile.kind)
if readyCond.Status != metav1.ConditionTrue {
return fmt.Errorf("%s reconciliation failed: ''%s", reconcile.kind, readyCond.Message)
}
logger.Successf(reconcile.object.successMessage())
return nil
@ -133,7 +135,9 @@ func reconciliationHandled(ctx context.Context, kubeClient client.Client,
if err != nil {
return false, err
}
return obj.lastHandledReconcileRequest() != lastHandledReconcileAt, nil
isProgressing := apimeta.IsStatusConditionPresentAndEqual(*obj.GetStatusConditions(),
meta.ReadyCondition, metav1.ConditionUnknown)
return obj.lastHandledReconcileRequest() != lastHandledReconcileAt && !isProgressing, nil
}
}

@ -20,7 +20,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)

@ -6,6 +6,7 @@ import (
"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"
@ -68,22 +69,26 @@ func (reconcile reconcileWithSourceCommand) run(cmd *cobra.Command, args []strin
rootArgs.namespace = nsCopy
}
lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()
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)
readyCond := apimeta.FindStatusCondition(*reconcile.object.GetStatusConditions(), meta.ReadyCondition)
if readyCond == nil {
return fmt.Errorf("status can't be determined")
}
if readyCond.Status != metav1.ConditionTrue {
return fmt.Errorf("%s reconciliation failed: %s", reconcile.kind, readyCond.Message)
}
logger.Successf(reconcile.object.successMessage())
return nil

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

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

@ -1,4 +1,3 @@
► checking prerequisites
✔ kubectl {{ .clientVersion }} >=1.18.0-0
✔ Kubernetes {{ .serverVersion }} >=1.16.0-0
✔ Kubernetes {{ .serverVersion }} >=1.19.0-0
✔ prerequisites checks passed

@ -1,10 +1,8 @@
► annotating GitRepository thrfg in {{ .ns }} namespace
✔ GitRepository annotated
◎ waiting for GitRepository reconciliation
✔ GitRepository reconciliation completed
✔ fetched revision 6.0.0/627d5c4bb67b77185f37e31d734b085019ff2951
► annotating HelmRelease thrfg in {{ .ns }} namespace
✔ HelmRelease annotated
◎ waiting for HelmRelease reconciliation
✔ HelmRelease reconciliation completed
✔ applied revision 6.0.0

@ -1,10 +1,8 @@
► annotating GitRepository tkfg in {{ .ns }} namespace
✔ GitRepository annotated
◎ waiting for GitRepository reconciliation
✔ GitRepository reconciliation completed
✔ fetched revision 6.0.0/627d5c4bb67b77185f37e31d734b085019ff2951
► annotating Kustomization tkfg in {{ .ns }} namespace
✔ Kustomization annotated
◎ waiting for Kustomization reconciliation
✔ Kustomization reconciliation completed
✔ applied revision 6.0.0/627d5c4bb67b77185f37e31d734b085019ff2951

@ -33,7 +33,7 @@ import (
"github.com/fluxcd/flux2/internal/utils"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
fluxmeta "github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)

@ -32,7 +32,7 @@ import (
"github.com/fluxcd/flux2/internal/utils"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)

@ -1,42 +1,129 @@
/*
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"
"encoding/json"
"fmt"
"strings"
"github.com/spf13/cobra"
v1 "k8s.io/api/apps/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
)
func getVersion(input string) (string, error) {
if input == "" {
return rootArgs.defaults.Version, nil
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the client and server-side components version information.",
Long: "Print the client and server-side components version information for the current context.",
Example: `# Print client and server-side version
flux version
# Print only client version
flux version --client
# Print information in json format
flux version -o json
`,
RunE: versionCmdRun,
}
type versionFlags struct {
client bool
output string
}
var versionArgs versionFlags
if isEmbeddedVersion(input) {
return input, nil
func init() {
versionCmd.Flags().BoolVar(&versionArgs.client, "client", false,
"print only client version")
versionCmd.Flags().StringVarP(&versionArgs.output, "output", "o", "yaml",
"the format in which the information should be printed. can be 'json' or 'yaml'")
rootCmd.AddCommand(versionCmd)
}
func versionCmdRun(cmd *cobra.Command, args []string) error {
if versionArgs.output != "yaml" && versionArgs.output != "json" {
return fmt.Errorf("--output must be json or yaml, not %s", versionArgs.output)
}
var err error
if input == install.MakeDefaultOptions().Version {
input, err = install.GetLatestVersion()
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
info := map[string]string{}
info["flux"] = rootArgs.defaults.Version
if !versionArgs.client {
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return "", err
return err
}
} else {
if ok, err := install.ExistingVersion(input); err != nil || !ok {
if err == nil {
err = fmt.Errorf("targeted version '%s' does not exist", input)
selector := client.MatchingLabels{"app.kubernetes.io/instance": rootArgs.namespace}
var list v1.DeploymentList
if err := kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace), selector); err != nil {
return err
}
if len(list.Items) == 0 {
return fmt.Errorf("no deployments found in %s namespace", rootArgs.namespace)
}
for _, d := range list.Items {
for _, c := range d.Spec.Template.Spec.Containers {
name, tag, err := splitImageStr(c.Image)
if err != nil {
return err
}
info[name] = tag
}
return "", err
}
}
if !utils.CompatibleVersion(VERSION, input) {
return "", fmt.Errorf("targeted version '%s' is not compatible with your current version of flux (%s)", input, VERSION)
var marshalled []byte
var err error
if versionArgs.output == "json" {
marshalled, err = json.MarshalIndent(&info, "", " ")
marshalled = append(marshalled, "\n"...)
} else {
marshalled, err = yaml.Marshal(&info)
}
if err != nil {
return err
}
return input, nil
rootCmd.Print(string(marshalled))
return nil
}
func isEmbeddedVersion(input string) bool {
return input == rootArgs.defaults.Version
func splitImageStr(image string) (string, string, error) {
imageArr := strings.Split(image, ":")
if len(imageArr) < 2 {
return "", "", fmt.Errorf("missing image tag in image %s", image)
}
name, tag := imageArr[0], imageArr[1]
nameArr := strings.Split(name, "/")
return nameArr[len(nameArr)-1], tag, nil
}

@ -1,15 +0,0 @@
// +build unit
package main
import (
"testing"
)
func TestVersion(t *testing.T) {
cmd := cmdTestCase{
args: "--version",
assert: assertGoldenValue("flux version 0.0.0-dev.0\n"),
}
cmd.runTestCmd(t)
}

@ -0,0 +1,58 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
)
func getVersion(input string) (string, error) {
if input == "" {
return rootArgs.defaults.Version, nil
}
if isEmbeddedVersion(input) {
return input, nil
}
var err error
if input == install.MakeDefaultOptions().Version {
input, err = install.GetLatestVersion()
if err != nil {
return "", err
}
} else {
if ok, err := install.ExistingVersion(input); err != nil || !ok {
if err == nil {
err = fmt.Errorf("targeted version '%s' does not exist", input)
}
return "", err
}
}
if !utils.CompatibleVersion(VERSION, input) {
return "", fmt.Errorf("targeted version '%s' is not compatible with your current version of flux (%s)", input, VERSION)
}
return input, nil
}
func isEmbeddedVersion(input string) bool {
return input == rootArgs.defaults.Version
}

@ -0,0 +1,55 @@
// +build unit
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestVersion(t *testing.T) {
cmd := cmdTestCase{
args: "--version",
assert: assertGoldenValue("flux version 0.0.0-dev.0\n"),
}
cmd.runTestCmd(t)
}
func TestVersionCmd(t *testing.T) {
tests := []struct {
args string
expected string
}{
{
args: "version --client",
expected: "flux: v0.0.0-dev.0\n",
},
{
args: "version --client -o json",
expected: "{\n \"flux\": \"v0.0.0-dev.0\"\n}\n",
},
}
for _, tt := range tests {
cmd := cmdTestCase{
args: tt.args,
assert: assertGoldenValue(tt.expected),
}
cmd.runTestCmd(t)
}
}

@ -4,21 +4,23 @@ go 1.16
require (
github.com/Masterminds/semver/v3 v3.1.0
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7
github.com/cyphar/filepath-securejoin v0.2.2
github.com/fluxcd/go-git-providers v0.1.1
github.com/fluxcd/helm-controller/api v0.11.2
github.com/fluxcd/image-automation-controller/api v0.14.1
github.com/fluxcd/image-reflector-controller/api v0.11.1
github.com/fluxcd/kustomize-controller/api v0.14.1
github.com/fluxcd/notification-controller/api v0.16.0
github.com/fluxcd/helm-controller/api v0.12.0
github.com/fluxcd/image-automation-controller/api v0.15.0
github.com/fluxcd/image-reflector-controller/api v0.12.0
github.com/fluxcd/kustomize-controller/api v0.15.2
github.com/fluxcd/notification-controller/api v0.17.0
github.com/fluxcd/pkg/apis/meta v0.10.0
github.com/fluxcd/pkg/runtime v0.12.0
github.com/fluxcd/pkg/ssa v0.0.3
github.com/fluxcd/pkg/ssh v0.0.5
github.com/fluxcd/pkg/untar v0.0.5
github.com/fluxcd/pkg/version v0.0.1
github.com/fluxcd/source-controller/api v0.15.4
github.com/fluxcd/source-controller/api v0.16.0
github.com/go-git/go-git/v5 v5.4.2
github.com/google/go-cmp v0.5.5
github.com/google/go-cmp v0.5.6
github.com/google/go-containerregistry v0.2.0
github.com/manifoldco/promptui v0.7.0
github.com/mattn/go-shellwords v1.0.12
@ -26,15 +28,15 @@ require (
github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
k8s.io/api v0.21.3
k8s.io/apiextensions-apiserver v0.21.3
k8s.io/apimachinery v0.21.3
k8s.io/client-go v0.21.3
k8s.io/api v0.22.2
k8s.io/apiextensions-apiserver v0.22.2
k8s.io/apimachinery v0.22.2
k8s.io/client-go v0.22.2
k8s.io/kubectl v0.21.1
sigs.k8s.io/cli-utils v0.25.1-0.20210608181808-f3974341173a
sigs.k8s.io/controller-runtime v0.9.5
sigs.k8s.io/controller-runtime v0.10.1
sigs.k8s.io/kustomize/api v0.8.10
sigs.k8s.io/yaml v1.2.0
sigs.k8s.io/yaml v1.3.0
)
// drop LGPL dependency manifoldco/promptui -> juju/ansiterm

173
go.sum

@ -28,18 +28,22 @@ github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v42.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
github.com/Azure/go-autorest/autorest v0.10.2/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=
github.com/Azure/go-autorest/autorest v0.11.12 h1:gI8ytXbxMfI+IVbI9mP2JGCTXIuhHLgRlvQ9X4PsnHE=
github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM=
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
@ -54,8 +58,9 @@ github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsI
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
@ -96,6 +101,7 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@ -112,6 +118,7 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
github.com/aws/aws-sdk-go v1.28.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -124,6 +131,8 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
@ -138,7 +147,11 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@ -149,6 +162,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
@ -193,6 +207,8 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
@ -203,18 +219,19 @@ github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQL
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/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/go-git-providers v0.1.1 h1:R4VafMOo1IlfEZcImApCeElge/HajhFvRzDKGlot+/c=
github.com/fluxcd/go-git-providers v0.1.1/go.mod h1:nRgNpHZmZhrsyNSma1JcAhjUG9xrqMGJcIUr9K7M7vk=
github.com/fluxcd/helm-controller/api v0.11.2 h1:IpzGEexjEa9DttikHF40kHFORhsxAeD2Z6SN6BZBA6o=
github.com/fluxcd/helm-controller/api v0.11.2/go.mod h1:gLPpLa8NMQJ+b4pwP/sExAPkGdFWO1CKIh1Uu82O2gM=
github.com/fluxcd/image-automation-controller/api v0.14.1 h1:8EDUs61Gi5HgSA9ou0rgFuDgvag+wpTrndizFhCGYwY=
github.com/fluxcd/image-automation-controller/api v0.14.1/go.mod h1:22GZblh0CmaZItQpvCBe40i5ql/oCZllpLqkGmoglEQ=
github.com/fluxcd/image-reflector-controller/api v0.11.1 h1:8pmUKL7Pise0JOBFgqw7eWtOK/rs3HNibXqCK9aJ8LE=
github.com/fluxcd/image-reflector-controller/api v0.11.1/go.mod h1:lgQHGFz29OHmDU5Jwg689C/M+P/f9ujt6NS0zCLT0BQ=
github.com/fluxcd/kustomize-controller/api v0.14.1 h1:OsErJQ3U3ReYTAtkeFo1t8UW4sjISF0a+6wsz942MT0=
github.com/fluxcd/kustomize-controller/api v0.14.1/go.mod h1:3RNiEd/XnYjSTGzMqDzDbQkOYpdPFrKuS+XdgWt9pds=
github.com/fluxcd/notification-controller/api v0.16.0 h1:3vaIj3AJRUA4dsfISuok8URV1RUmoe9NFpCAZ+tjOeU=
github.com/fluxcd/notification-controller/api v0.16.0/go.mod h1:t28GMWMLiLqho+ikpZrldv22/vmCsFdQR8vdJluxknc=
github.com/fluxcd/helm-controller/api v0.12.0 h1:68GKGZ5dHvOt4rx6gwQaOGliUksv7F/q8JQo2c0Tcis=
github.com/fluxcd/helm-controller/api v0.12.0/go.mod h1:zWmzV0s2SU4rEIGLPTt+dsaMs40OsNQgSgOATgJmxB0=
github.com/fluxcd/image-automation-controller/api v0.15.0 h1:KI350vt5JahE43D17VyLZFH4ZxtbnyHrekAd8AJsT5E=
github.com/fluxcd/image-automation-controller/api v0.15.0/go.mod h1:XvrEEpM1rVU+x1gQeXB/dj56w1dmOJRraTxQWOiuNME=
github.com/fluxcd/image-reflector-controller/api v0.12.0 h1:ghdbOUmbNaPi/jVfNnI3xzvhudXBZx6xUld/OLNSVXM=
github.com/fluxcd/image-reflector-controller/api v0.12.0/go.mod h1:lgQHGFz29OHmDU5Jwg689C/M+P/f9ujt6NS0zCLT0BQ=
github.com/fluxcd/kustomize-controller/api v0.15.2 h1:6MEt9ke7eQeRZsFPv+OzPeXuURS9axCIFgV3KopDOmo=
github.com/fluxcd/kustomize-controller/api v0.15.2/go.mod h1:OhnZuXBeDl4NqbDZgpYKRg8nmsmeUIddH3vX8wxym9A=
github.com/fluxcd/notification-controller/api v0.17.0 h1:HrTX6EThmvbW3I+uKJyaggcNnRsLxogc6q7A3nXZ9tQ=
github.com/fluxcd/notification-controller/api v0.17.0/go.mod h1:t28GMWMLiLqho+ikpZrldv22/vmCsFdQR8vdJluxknc=
github.com/fluxcd/pkg/apis/kustomize v0.1.0/go.mod h1:gEl+W5cVykCC3RfrCaqe+Pz+j4lKl2aeR4dxsom/zII=
github.com/fluxcd/pkg/apis/kustomize v0.2.0 h1:jhu2QHvs+j3Zo9rR6w8hkO3LSC6h3M37zY5ejufOmxY=
github.com/fluxcd/pkg/apis/kustomize v0.2.0/go.mod h1:gEl+W5cVykCC3RfrCaqe+Pz+j4lKl2aeR4dxsom/zII=
@ -222,23 +239,27 @@ github.com/fluxcd/pkg/apis/meta v0.10.0 h1:N7wVGHC1cyPdT87hrDC7UwCwRwnZdQM46PBSL
github.com/fluxcd/pkg/apis/meta v0.10.0/go.mod h1:CW9X9ijMTpNe7BwnokiUOrLl/h13miwVr/3abEQLbKE=
github.com/fluxcd/pkg/runtime v0.12.0 h1:BPZZ8bBkimpqGAPXqOf3LTaw+tcw6HgbWyCuzbbsJGs=
github.com/fluxcd/pkg/runtime v0.12.0/go.mod h1:EyaTR2TOYcjL5U//C4yH3bt2tvTgIOSXpVRbWxUn/C4=
github.com/fluxcd/pkg/ssa v0.0.3 h1:5s/dZIaJGtI/46w5VRmr5sy/jNJGpxPnSad8oSzAbkc=
github.com/fluxcd/pkg/ssa v0.0.3/go.mod h1:B/6V2gF3zq3P6EstM/qTmOEUf3YgOh1ybFfAAmG4Shw=
github.com/fluxcd/pkg/ssh v0.0.5 h1:rnbFZ7voy2JBlUfMbfyqArX2FYaLNpDhccGFC3qW83A=
github.com/fluxcd/pkg/ssh v0.0.5/go.mod h1:7jXPdXZpc0ttMNz2kD9QuMi3RNn/e0DOFbj0Tij/+Hs=
github.com/fluxcd/pkg/untar v0.0.5 h1:UGI3Ch1UIEIaqQvMicmImL1s9npQa64DJ/ozqHKB7gk=
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/go.mod h1:WAF4FEEA9xyhngF8TDxg3UPu5fA1qhEYV8Pmi2Il01Q=
github.com/fluxcd/source-controller/api v0.15.4 h1:9aRcH/WKJWt7Bp954K/wzLRuiRiHuD2osvYp74GoP64=
github.com/fluxcd/source-controller/api v0.15.4/go.mod h1:guUCCapjzE2kocwFreQTM/IGvtAglIJc4L97mokairo=
github.com/fluxcd/source-controller/api v0.16.0 h1:xFz+K7lLg/82uOQp+a0g04GsgoWNfyzwXAoVQy4T/oI=
github.com/fluxcd/source-controller/api v0.16.0/go.mod h1:guUCCapjzE2kocwFreQTM/IGvtAglIJc4L97mokairo=
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/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
@ -285,14 +306,16 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
@ -316,8 +339,9 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
@ -326,6 +350,7 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -339,8 +364,9 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -363,12 +389,14 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -377,8 +405,9 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/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-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/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/go.mod h1:Ts3Wioz1r5ayWx8sS6vLcWltWcM1aqFjd/eVrkFhrWM=
github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=
@ -426,9 +455,11 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJr
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.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
@ -454,7 +485,6 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
@ -478,6 +508,9 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -525,8 +558,9 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
@ -556,6 +590,7 @@ github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:F
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -610,8 +645,9 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.12.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI=
github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
@ -680,6 +716,7 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
@ -701,9 +738,11 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@ -742,6 +781,7 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@ -758,11 +798,20 @@ github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6Ut
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
@ -772,6 +821,18 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -791,8 +852,9 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -837,6 +899,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@ -846,6 +909,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -887,11 +951,14 @@ 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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -910,6 +977,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -959,6 +1027,7 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -968,12 +1037,17 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo=
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
@ -985,6 +1059,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1049,6 +1124,7 @@ golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4X
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1102,11 +1178,14 @@ google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@ -1121,6 +1200,10 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -1160,6 +1243,7 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -1184,32 +1268,46 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY=
k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s=
k8s.io/api v0.21.3 h1:cblWILbLO8ar+Fj6xdDGr603HRsf8Wu9E9rngJeprZQ=
k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg=
k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY=
k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw=
k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8=
k8s.io/apiextensions-apiserver v0.21.1/go.mod h1:KESQFCGjqVcVsZ9g0xX5bacMjyX5emuWcS2arzdEouA=
k8s.io/apiextensions-apiserver v0.21.3 h1:+B6biyUWpqt41kz5x6peIsljlsuwvNAp/oFax/j2/aY=
k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE=
k8s.io/apiextensions-apiserver v0.22.1/go.mod h1:HeGmorjtRmRLE+Q8dJu6AYRoZccvCMsghwS8XTUYb2c=
k8s.io/apiextensions-apiserver v0.22.2 h1:zK7qI8Ery7j2CaN23UCFaC1hj7dMiI87n01+nKuewd4=
k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA=
k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig=
k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
k8s.io/apimachinery v0.21.3 h1:3Ju4nvjCngxxMYby0BimUk+pQHPOQp3eCGChk5kfVII=
k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI=
k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk=
k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM=
k8s.io/apiserver v0.21.1/go.mod h1:nLLYZvMWn35glJ4/FZRhzLG/3MPxAaZTgV4FJZdr+tY=
k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU=
k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400=
k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI=
k8s.io/cli-runtime v0.21.1 h1:Oj/iZxa7LLXrhzShaLNF4rFJEIEBTDHj0dJw4ra2vX4=
k8s.io/cli-runtime v0.21.1/go.mod h1:TI9Bvl8lQWZB2KqE91QLCp9AZE4l29zNFnj/x4IX4Fw=
k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU=
k8s.io/client-go v0.21.1/go.mod h1:/kEw4RgW+3xnBGzvp9IWxKSNA+lXn3A7AuH3gdOAzLs=
k8s.io/client-go v0.21.3 h1:J9nxZTOmvkInRDCzcSNQmPJbDYN/PjlxXT9Mos3HcLg=
k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU=
k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk=
k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc=
k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U=
k8s.io/cloud-provider v0.18.8/go.mod h1:cn9AlzMPVIXA4HHLVbgGUigaQlZyHSZ7WAwDEFNrQSs=
k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q=
k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo=
k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU=
k8s.io/component-base v0.21.1/go.mod h1:NgzFZ2qu4m1juby4TnrmpR8adRk6ka62YdH5DkIIyKA=
k8s.io/component-base v0.21.3 h1:4WuuXY3Npa+iFfi2aDRiOz+anhNvRfye0859ZgfC5Og=
k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ=
k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo=
k8s.io/component-base v0.22.2 h1:vNIvE0AIrLhjX8drH0BgCNJcR4QZxMXcJzBsDplDx9M=
k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug=
k8s.io/component-helpers v0.21.1/go.mod h1:FtC1flbiQlosHQrLrRUulnKxE4ajgWCGy/67fT2GRlQ=
k8s.io/csi-translation-lib v0.18.8/go.mod h1:6cA6Btlzxy9s3QrS4BCZzQqclIWnTLr6Jx3H2ctAzY4=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
@ -1238,8 +1336,10 @@ k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210517184530-5a248b5acedc/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210527160623-6fdb442a123b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 h1:DnzUXII7sVg1FJ/4JX6YDRJfLNAC7idRatPwe07suiI=
k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g=
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
@ -1251,12 +1351,14 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/cli-utils v0.25.1-0.20210608181808-f3974341173a h1:S17+FPWGsOonXim+GcadLcSblEwL131Y9fKLfifSYkY=
sigs.k8s.io/cli-utils v0.25.1-0.20210608181808-f3974341173a/go.mod h1:I4jgHr6uRfX0CkOMECwSgg2J48rNzZE1+kDXj9SnJBc=
sigs.k8s.io/controller-runtime v0.9.0-beta.5.0.20210524185538-7181f1162e79/go.mod h1:rgf+cBz72pYlKXDRNhI1WFQv/S86EMUV4/ySmsEYgHk=
sigs.k8s.io/controller-runtime v0.9.0/go.mod h1:TgkfvrhhEw3PlI0BRL/5xM+89y3/yc0ZDfdbTl84si8=
sigs.k8s.io/controller-runtime v0.9.5 h1:WThcFE6cqctTn2jCZprLICO6BaKZfhsT37uAapTNfxc=
sigs.k8s.io/controller-runtime v0.9.5/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA=
sigs.k8s.io/controller-runtime v0.10.1 h1:+eLHgY/VrJWnfg6iXUqhCUqNXgPH1NZeP9drNAAgWlg=
sigs.k8s.io/controller-runtime v0.10.1/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY=
sigs.k8s.io/kustomize/api v0.8.8/go.mod h1:He1zoK0nk43Pc6NlV085xDXDXTNprtcyKZVm3swsdNY=
sigs.k8s.io/kustomize/api v0.8.10 h1:CqbdK/qT7JE+uVETkrVMk7pQf0fPFXk9+QQ//Q7sAtc=
sigs.k8s.io/kustomize/api v0.8.10/go.mod h1:ImeIkhUU7GIhamOtKPlkllt+fkBKL5f6/4NLhVwkinA=
@ -1274,6 +1376,7 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno=
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

@ -30,7 +30,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/pkg/manifestgen/install"

@ -33,7 +33,7 @@ import (
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/yaml"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/flux2/internal/bootstrap/git"
"github.com/fluxcd/flux2/internal/utils"
@ -53,6 +53,10 @@ type PlainGitBootstrapper struct {
author git.Author
commitMessageAppendix string
gpgKeyRingPath string
gpgPassphrase string
gpgKeyID string
kubeconfig string
kubecontext string
@ -142,6 +146,7 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
}
// Git commit generated
gpgOpts := git.WithGpgSigningOption(b.gpgKeyRingPath, b.gpgPassphrase, b.gpgKeyID)
commitMsg := fmt.Sprintf("Add Flux %s component manifests", options.Version)
if b.commitMessageAppendix != "" {
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix
@ -149,7 +154,7 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
commit, err := b.git.Commit(git.Commit{
Author: b.author,
Message: commitMsg,
})
}, gpgOpts)
if err != nil && err != git.ErrNoStagedFiles {
return fmt.Errorf("failed to commit sync manifests: %w", err)
}
@ -170,41 +175,14 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
// Apply components using any existing customisations
kfile := filepath.Join(filepath.Dir(componentsYAML), konfig.DefaultKustomizationFileName())
if _, err := os.Stat(kfile); err == nil {
tmpDir, err := os.MkdirTemp("", "gotk-crds")
defer os.RemoveAll(tmpDir)
// Extract the CRDs from the components manifest
crdsYAML := filepath.Join(tmpDir, "gotk-crds.yaml")
if err := utils.ExtractCRDs(componentsYAML, crdsYAML); err != nil {
return err
}
// Apply the CRDs
b.logger.Actionf("installing toolkit.fluxcd.io CRDs")
kubectlArgs := []string{"apply", "-f", crdsYAML}
if _, err = utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, b.kubeconfig, b.kubecontext, kubectlArgs...); err != nil {
return err
}
// Wait for CRDs to be established
b.logger.Waitingf("waiting for CRDs to be reconciled")
kubectlArgs = []string{"wait", "--for", "condition=established", "-f", crdsYAML}
if _, err = utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, b.kubeconfig, b.kubecontext, kubectlArgs...); err != nil {
return err
}
b.logger.Successf("CRDs reconciled successfully")
// Apply the components and their patches
b.logger.Actionf("installing components in %q namespace", options.Namespace)
kubectlArgs = []string{"apply", "-k", filepath.Dir(componentsYAML)}
if _, err = utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, b.kubeconfig, b.kubecontext, kubectlArgs...); err != nil {
if _, err := utils.Apply(ctx, b.kubeconfig, b.kubecontext, kfile); err != nil {
return err
}
} else {
// Apply the CRDs and controllers
b.logger.Actionf("installing components in %q namespace", options.Namespace)
kubectlArgs := []string{"apply", "-f", componentsYAML}
if _, err = utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, b.kubeconfig, b.kubecontext, kubectlArgs...); err != nil {
if _, err := utils.Apply(ctx, b.kubeconfig, b.kubecontext, componentsYAML); err != nil {
return err
}
}
@ -306,6 +284,7 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
b.logger.Successf("generated sync manifests")
// Git commit generated
gpgOpts := git.WithGpgSigningOption(b.gpgKeyRingPath, b.gpgPassphrase, b.gpgKeyID)
commitMsg := fmt.Sprintf("Add Flux sync manifests")
if b.commitMessageAppendix != "" {
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix
@ -313,7 +292,8 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
commit, err := b.git.Commit(git.Commit{
Author: b.author,
Message: commitMsg,
})
}, gpgOpts)
if err != nil && err != git.ErrNoStagedFiles {
return fmt.Errorf("failed to commit sync manifests: %w", err)
}
@ -329,10 +309,10 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
// 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 {
if _, err := utils.Apply(ctx, b.kubeconfig, b.kubecontext, filepath.Join(b.git.Path(), kusManifests.Path)); err != nil {
return err
}
b.logger.Successf("reconciled sync configuration")
return nil

@ -318,6 +318,10 @@ func (b *GitProviderBootstrapper) reconcileOrgRepository(ctx context.Context) (g
b.logger.Actionf("reconciling repository permissions")
for _, i := range teamAccessInfo {
var err error
// Don't reconcile team if team already exists and b.reconcile is false
if team, err := repo.TeamAccess().Get(ctx, i.Name); err == nil && !b.reconcile && team != nil {
continue
}
_, changed, err = repo.TeamAccess().Reconcile(ctx, i)
if err != nil {
warning = fmt.Errorf("failed to grant permissions to team: %w", ErrReconciledWithWarning)

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

@ -44,7 +44,7 @@ type Git interface {
Init(url, branch string) (bool, error)
Clone(ctx context.Context, url, branch string, caBundle []byte) (bool, error)
Write(path string, reader io.Reader) error
Commit(message Commit) (string, error)
Commit(message Commit, options ...Option) (string, error)
Push(ctx context.Context, caBundle []byte) error
Status() (bool, error)
Head() (string, error)

@ -25,6 +25,7 @@ import (
"strings"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
gogit "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
@ -40,6 +41,12 @@ type GoGit struct {
repository *gogit.Repository
}
type CommitOptions struct {
GpgKeyPath string
GpgKeyPassphrase string
KeyID string
}
func New(path string, auth transport.AuthMethod) *GoGit {
return &GoGit{
path: path,
@ -127,7 +134,7 @@ func (g *GoGit) Write(path string, reader io.Reader) error {
return err
}
func (g *GoGit) Commit(message git.Commit) (string, error) {
func (g *GoGit) Commit(message git.Commit, opts ...git.Option) (string, error) {
if g.repository == nil {
return "", git.ErrNoGitRepository
}
@ -142,6 +149,12 @@ func (g *GoGit) Commit(message git.Commit) (string, error) {
return "", err
}
// apply the options
options := &git.CommitOptions{}
for _, opt := range opts {
opt.ApplyToCommit(options)
}
// go-git has [a bug](https://github.com/go-git/go-git/issues/253)
// whereby it thinks broken symlinks to absolute paths are
// modified. There's no circumstance in which we want to commit a
@ -173,13 +186,24 @@ func (g *GoGit) Commit(message git.Commit) (string, error) {
return head.Hash().String(), git.ErrNoStagedFiles
}
commit, err := wt.Commit(message.Message, &gogit.CommitOptions{
commitOpts := &gogit.CommitOptions{
Author: &object.Signature{
Name: message.Name,
Email: message.Email,
When: time.Now(),
},
})
}
if options.GPGSigningInfo != nil {
entity, err := getOpenPgpEntity(*options.GPGSigningInfo)
if err != nil {
return "", err
}
commitOpts.SignKey = entity
}
commit, err := wt.Commit(message.Message, commitOpts)
if err != nil {
return "", err
}
@ -232,3 +256,41 @@ func (g *GoGit) Path() string {
func isRemoteBranchNotFoundErr(err error, ref string) bool {
return strings.Contains(err.Error(), fmt.Sprintf("couldn't find remote ref %q", ref))
}
func getOpenPgpEntity(info git.GPGSigningInfo) (*openpgp.Entity, error) {
r, err := os.Open(info.KeyRingPath)
if err != nil {
return nil, fmt.Errorf("unable to open GPG key ring: %w", err)
}
entityList, err := openpgp.ReadKeyRing(r)
if err != nil {
return nil, err
}
if len(entityList) == 0 {
return nil, fmt.Errorf("empty GPG key ring")
}
var entity *openpgp.Entity
if info.KeyID != "" {
for _, ent := range entityList {
if ent.PrimaryKey.KeyIdString() == info.KeyID {
entity = ent
}
}
if entity == nil {
return nil, fmt.Errorf("no GPG private key matching key id '%s' found", info.KeyID)
}
} else {
entity = entityList[0]
}
err = entity.PrivateKey.Decrypt([]byte(info.Passphrase))
if err != nil {
return nil, fmt.Errorf("unable to decrypt GPG private key: %w", err)
}
return entity, nil
}

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

@ -112,3 +112,27 @@ func (o loggerOption) applyGit(b *PlainGitBootstrapper) {
func (o loggerOption) applyGitProvider(b *GitProviderBootstrapper) {
b.logger = o.logger
}
func WithGitCommitSigning(path, passphrase, keyID string) Option {
return gitCommitSigningOption{
gpgKeyRingPath: path,
gpgPassphrase: passphrase,
gpgKeyID: keyID,
}
}
type gitCommitSigningOption struct {
gpgKeyRingPath string
gpgPassphrase string
gpgKeyID string
}
func (o gitCommitSigningOption) applyGit(b *PlainGitBootstrapper) {
b.gpgKeyRingPath = o.gpgKeyRingPath
b.gpgPassphrase = o.gpgPassphrase
b.gpgKeyID = o.gpgKeyID
}
func (o gitCommitSigningOption) applyGitProvider(b *GitProviderBootstrapper) {
o.applyGit(b.PlainGitBootstrapper)
}

@ -0,0 +1,85 @@
package utils
import (
"bufio"
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"time"
"github.com/fluxcd/pkg/ssa"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/kustomize/api/konfig"
"github.com/fluxcd/flux2/pkg/manifestgen/kustomization"
)
// Apply is the equivalent of 'kubectl apply --server-side -f'.
// If the given manifest is a kustomization.yaml, then apply performs the equivalent of 'kubectl apply --server-side -k'.
func Apply(ctx context.Context, kubeConfigPath string, kubeContext string, manifestPath string) (string, error) {
cfg, err := KubeConfig(kubeConfigPath, kubeContext)
if err != nil {
return "", err
}
restMapper, err := apiutil.NewDynamicRESTMapper(cfg)
if err != nil {
return "", err
}
kubeClient, err := client.New(cfg, client.Options{Mapper: restMapper})
if err != nil {
return "", err
}
kubePoller := polling.NewStatusPoller(kubeClient, restMapper)
resourceManager := ssa.NewResourceManager(kubeClient, kubePoller, ssa.Owner{
Field: "flux",
Group: "fluxcd.io",
})
objs, err := readObjects(manifestPath)
if err != nil {
return "", err
}
if len(objs) < 1 {
return "", fmt.Errorf("no Kubernetes objects found at: %s", manifestPath)
}
if err := ssa.SetNativeKindsDefaults(objs); err != nil {
return "", err
}
changeSet, err := resourceManager.ApplyAllStaged(ctx, objs, false, time.Minute)
if err != nil {
return "", err
}
return changeSet.String(), nil
}
func readObjects(manifestPath string) ([]*unstructured.Unstructured, error) {
if _, err := os.Stat(manifestPath); err != nil {
return nil, err
}
if filepath.Base(manifestPath) == konfig.DefaultKustomizationFileName() {
resources, err := kustomization.Build(filepath.Dir(manifestPath))
if err != nil {
return nil, err
}
return ssa.ReadObjects(bytes.NewReader(resources))
}
ms, err := os.Open(manifestPath)
if err != nil {
return nil, err
}
defer ms.Close()
return ssa.ReadObjects(bufio.NewReader(ms))
}

@ -45,7 +45,7 @@ import (
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
imageautov1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
imagereflectv1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/runtime/dependency"
"github.com/fluxcd/pkg/version"

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

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

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

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

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

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

@ -1,9 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- https://github.com/fluxcd/source-controller/releases/download/v0.15.4/source-controller.crds.yaml
- https://github.com/fluxcd/kustomize-controller/releases/download/v0.14.1/kustomize-controller.crds.yaml
- https://github.com/fluxcd/helm-controller/releases/download/v0.11.2/helm-controller.crds.yaml
- https://github.com/fluxcd/notification-controller/releases/download/v0.16.0/notification-controller.crds.yaml
- https://github.com/fluxcd/image-reflector-controller/releases/download/v0.11.1/image-reflector-controller.crds.yaml
- https://github.com/fluxcd/image-automation-controller/releases/download/v0.14.1/image-automation-controller.crds.yaml
- https://github.com/fluxcd/source-controller/releases/download/v0.16.0/source-controller.crds.yaml
- https://github.com/fluxcd/kustomize-controller/releases/download/v0.15.2/kustomize-controller.crds.yaml
- https://github.com/fluxcd/helm-controller/releases/download/v0.12.0/helm-controller.crds.yaml
- https://github.com/fluxcd/notification-controller/releases/download/v0.17.0/notification-controller.crds.yaml
- https://github.com/fluxcd/image-reflector-controller/releases/download/v0.12.0/image-reflector-controller.crds.yaml
- https://github.com/fluxcd/image-automation-controller/releases/download/v0.15.0/image-automation-controller.crds.yaml

@ -7,7 +7,7 @@ spec:
template:
spec:
initContainers:
- image: bitnami/kubectl
- image: ghcr.io/fluxcd/flux-cli:v0.17.2
securityContext:
privileged: false
readOnlyRootFilesystem: true

@ -9,7 +9,7 @@ spec:
template:
spec:
initContainers:
- image: bitnami/kubectl
- image: ghcr.io/fluxcd/flux-cli:v0.17.2
name: copy-kubectl
# it's okay to do this because kubectl is a statically linked binary
command:

@ -7,7 +7,7 @@ spec:
template:
spec:
initContainers:
- image: bitnami/kubectl
- image: ghcr.io/fluxcd/flux-cli:v0.17.2
name: copy-kubectl
# it's okay to do this because kubectl is a statically linked binary
command:

@ -9,7 +9,7 @@ spec:
template:
spec:
initContainers:
- image: bitnami/kubectl
- image: ghcr.io/fluxcd/flux-cli:v0.17.2
name: copy-kubectl
# it's okay to do this because kubectl is a statically linked binary
command:

@ -25,13 +25,11 @@ import (
"path"
"path/filepath"
"strings"
"sync"
"github.com/fluxcd/pkg/untar"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/krusty"
kustypes "sigs.k8s.io/kustomize/api/types"
"github.com/fluxcd/pkg/untar"
"github.com/fluxcd/flux2/pkg/manifestgen/kustomization"
)
func fetch(ctx context.Context, url, version, dir string) error {
@ -114,56 +112,13 @@ func generate(base string, options Options) error {
return nil
}
var kustomizeBuildMutex sync.Mutex
func build(base, output string) error {
// TODO(stefan): temporary workaround for concurrent map read and map write bug
// https://github.com/kubernetes-sigs/kustomize/issues/3659
kustomizeBuildMutex.Lock()
defer kustomizeBuildMutex.Unlock()
kfile := filepath.Join(base, "kustomization.yaml")
fs := filesys.MakeFsOnDisk()
if !fs.Exists(kfile) {
return fmt.Errorf("%s not found", kfile)
}
// TODO(hidde): work around for a bug in kustomize causing it to
// not properly handle absolute paths on Windows.
// Convert the path to a relative path to the working directory
// as a temporary fix:
// https://github.com/kubernetes-sigs/kustomize/issues/2789
if filepath.IsAbs(base) {
wd, err := os.Getwd()
if err != nil {
return err
}
base, err = filepath.Rel(wd, base)
if err != nil {
return err
}
}
buildOptions := &krusty.Options{
DoLegacyResourceSort: true,
LoadRestrictions: kustypes.LoadRestrictionsNone,
AddManagedbyLabel: false,
DoPrune: false,
PluginConfig: kustypes.DisabledPluginConfig(),
}
k := krusty.MakeKustomizer(buildOptions)
m, err := k.Run(fs, base)
if err != nil {
return err
}
resources, err := m.AsYaml()
resources, err := kustomization.Build(base)
if err != nil {
return err
}
fs := filesys.MakeFsOnDisk()
if err := fs.WriteFile(output, resources); err != nil {
return err
}

@ -17,10 +17,14 @@ limitations under the License.
package kustomization
import (
"fmt"
"os"
"path/filepath"
"sync"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/krusty"
"sigs.k8s.io/kustomize/api/provider"
kustypes "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
@ -28,6 +32,8 @@ import (
"github.com/fluxcd/flux2/pkg/manifestgen"
)
// Generate scans the given directory for Kubernetes manifests and creates a kustomization.yaml
// including all discovered manifests as resources.
func Generate(options Options) (*manifestgen.Manifest, error) {
kfile := filepath.Join(options.TargetPath, konfig.DefaultKustomizationFileName())
abskfile := filepath.Join(options.BaseDir, kfile)
@ -121,3 +127,57 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
Content: string(kd),
}, nil
}
var kustomizeBuildMutex sync.Mutex
// Build takes a Kustomize overlays and returns the resulting manifests as multi-doc YAML.
func Build(base string) ([]byte, error) {
// TODO(stefan): temporary workaround for concurrent map read and map write bug
// https://github.com/kubernetes-sigs/kustomize/issues/3659
kustomizeBuildMutex.Lock()
defer kustomizeBuildMutex.Unlock()
kfile := filepath.Join(base, konfig.DefaultKustomizationFileName())
fs := filesys.MakeFsOnDisk()
if !fs.Exists(kfile) {
return nil, fmt.Errorf("%s not found", kfile)
}
// TODO(hidde): work around for a bug in kustomize causing it to
// not properly handle absolute paths on Windows.
// Convert the path to a relative path to the working directory
// as a temporary fix:
// https://github.com/kubernetes-sigs/kustomize/issues/2789
if filepath.IsAbs(base) {
wd, err := os.Getwd()
if err != nil {
return nil, err
}
base, err = filepath.Rel(wd, base)
if err != nil {
return nil, err
}
}
buildOptions := &krusty.Options{
DoLegacyResourceSort: true,
LoadRestrictions: kustypes.LoadRestrictionsNone,
AddManagedbyLabel: false,
DoPrune: false,
PluginConfig: kustypes.DisabledPluginConfig(),
}
k := krusty.MakeKustomizer(buildOptions)
m, err := k.Run(fs, base)
if err != nil {
return nil, err
}
resources, err := m.AsYaml()
if err != nil {
return nil, err
}
return resources, nil
}

@ -26,6 +26,9 @@ type Options struct {
Name string
Namespace string
Branch string
Tag string
SemVer string
Commit string
Secret string
TargetPath string
ManifestFile string

@ -26,7 +26,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
@ -35,6 +35,20 @@ import (
func Generate(options Options) (*manifestgen.Manifest, error) {
gvk := sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)
gitRef := &sourcev1.GitRepositoryRef{}
if options.Branch != "" {
gitRef.Branch = options.Branch
}
if options.Tag != "" {
gitRef.Tag = options.Tag
}
if options.SemVer != "" {
gitRef.SemVer = options.SemVer
}
if options.Commit != "" {
gitRef.Commit = options.Commit
}
gitRepository := sourcev1.GitRepository{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
@ -49,9 +63,7 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
Interval: metav1.Duration{
Duration: options.Interval,
},
Reference: &sourcev1.GitRepositoryRef{
Branch: options.Branch,
},
Reference: gitRef,
SecretRef: &meta.LocalObjectReference{
Name: options.Secret,
},
@ -85,7 +97,6 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
Kind: sourcev1.GitRepositoryKind,
Name: options.Name,
},
Validation: "client",
},
}

@ -20,10 +20,11 @@ package sync
import (
"fmt"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"strings"
"testing"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
func TestGenerate(t *testing.T) {

40
tests/.gitignore vendored

@ -0,0 +1,40 @@
# Created by https://www.toptal.com/developers/gitignore/api/terraform
# Edit at https://www.toptal.com/developers/gitignore?templates=terraform
### Terraform ###
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
# Exclude all .tfvars files, which are likely to contain sentitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
#
*.tfvars
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# End of https://www.toptal.com/developers/gitignore/api/terraform

@ -0,0 +1,51 @@
# Azure E2E
E2E tests for Azure are needed to mitigate introduction of new bugs in dependencies like libgit2. The goal is to verify that Flux integration with
Azure services are actually working now and in the future.
## Architecture
The tests are run with the help of pre configured Azure subscriptions and Azure DevOps organization. Access to thse accounts are currently limited to
Flux maintainers.
* [Azure Subscription](https://portal.azure.com/#@weaveworksendtoend.onmicrosoft.com/resource/subscriptions/71e8dce4-9af6-405a-8e96-425f5d3c302b/overview)
* [Azure DevOps organization](https://dev.azure.com/flux-azure/)
All infrastructure is and should be created with Terraform. There are two separate Terraform states. All state should be configured to use remote
state in Azure. They should all be placed in the [same container](https://portal.azure.com/#@weaveworksendtoend.onmicrosoft.com/resource/subscriptions/71e8dce4-9af6-405a-8e96-425f5d3c302b/resourceGroups/terraform-state/providers/Microsoft.Storage/storageAccounts/terraformstate0419/containersList)
but use different keys.
The [shared](./terraform/shared) Terraform creates long running cheaper infrastructure that is used across all tests. This includes a Key Vault which
contains an ssh key and Azure DevOps Personal Access Token which cant be created automatically. It also includes an Azure Container Registry which the
forked [podinfo](https://dev.azure.com/flux-azure/e2e/_git/podinfo) repository pushes an Helm Chart and Docker image to.
The [aks](./terraform/aks) Terraform creates the AKS cluster and related resources to run the tests. It creates the AKS cluster, Azure DevOps
repositories, Key Vault Key for Sops, and Azure EventHub. The resources should be created and destroyed before and after every test run. Currently
the same state is reused between runs to make sure that resources are left running after each test run.
## Tests
Each test run is intiated by running `terraform apply` on the aks Terraform, it does this by using the library [terraform-exec](github.com/hashicorp/terraform-exec).
It then reads the output of the Terraform to get credentials and ssh keys, this means that a lot of the communication with the Azure API is offest to
Terraform instead of requiring it to be implemented in the test.
The following tests are currently implemented:
- [x] Flux can be successfully installed on AKS using the CLI e.g.:
- [x] source-controller can clone Azure DevOps repositories (https+ssh)
- [x] image-reflector-controller can list tags from Azure Container Registry image repositories
- [x] kustomize-controller can decrypt secrets using SOPS and Azure Key Vault
- [x] image-automation-controller can create branches and push to Azure DevOps repositories (https+ssh)
- [x] notification-controller can send commit status to Azure DevOps
- [x] notification-controller can forward events to Azure Event Hub
- [x] source-controller can pull charts from Azure Container Registry Helm repositories
## Give User Access
There are a couple of steps required when adding a new user to get access to the Azure portal and Azure DevOps. To begin with add the new user to
[Azure AD](https://portal.azure.com/#blade/Microsoft_AAD_IAM/UsersManagementMenuBlade/MsGraphUsers), and add the user to the [Azure AD group
flux-contributors](https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupDetailsMenuBlade/Overview/groupId/24e0f3f6-6555-4d3d-99ab-414c869cab5d). The
new users should now go through the invite process, and be able to sign in to both the Azure Portal and Azure DevOps.
After the new user has signed into Azure DevOps you will need to modify the users permissions. This cannot be done before the user has signed in a
first time. In the [organization users page](https://dev.azure.com/flux-azure/_settings/users) chose "Manage User" and set the "Access Level" to basic
and make the user "Project Contributor" of the "e2e" Azure DevOps project.

@ -0,0 +1,884 @@
package test
import (
"bytes"
"context"
b64 "encoding/base64"
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"testing"
"time"
eventhub "github.com/Azure/azure-event-hubs-go/v3"
"github.com/hashicorp/terraform-exec/tfexec"
"github.com/hashicorp/terraform-exec/tfinstall"
git2go "github.com/libgit2/git2go/v31"
"github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
"github.com/stretchr/testify/require"
giturls "github.com/whilp/git-urls"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
automationv1beta1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
reflectorv1beta1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
notiv1beta1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/events"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
type config struct {
kubeconfigPath string
kubeClient client.Client
azdoPat string
idRsa string
idRsaPub string
knownHosts string
fleetInfraRepository repoConfig
applicationRepository repoConfig
fluxAzureSp spConfig
sopsId string
acr acrConfig
eventHubSas string
}
type spConfig struct {
tenantId string
clientId string
clientSecret string
}
type repoConfig struct {
http string
ssh string
}
type acrConfig struct {
url string
username string
password string
}
var cfg config
func TestMain(m *testing.M) {
ctx := context.TODO()
log.Println("Setting up Azure test infrastructure")
execPath, err := tfinstall.Find(ctx, &whichTerraform{})
if err != nil {
log.Fatalf("terraform exec path not found: %v", err)
}
tf, err := tfexec.NewTerraform("./terraform/aks", execPath)
if err != nil {
log.Fatalf("could not create terraform instance: %v", err)
}
log.Println("Init Terraform")
err = tf.Init(ctx, tfexec.Upgrade(true))
if err != nil {
log.Fatalf("error running init: %v", err)
}
log.Println("Applying Terraform")
err = tf.Apply(ctx)
if err != nil {
log.Fatalf("error running apply: %v", err)
}
state, err := tf.Show(ctx)
if err != nil {
log.Fatalf("error running show: %v", err)
}
outputs := state.Values.Outputs
kubeconfig := outputs["aks_kube_config"].Value.(string)
aksHost := outputs["aks_host"].Value.(string)
aksCert := outputs["aks_client_certificate"].Value.(string)
aksKey := outputs["aks_client_key"].Value.(string)
aksCa := outputs["aks_cluster_ca_certificate"].Value.(string)
azdoPat := outputs["shared_pat"].Value.(string)
idRsa := outputs["shared_id_rsa"].Value.(string)
idRsaPub := outputs["shared_id_rsa_pub"].Value.(string)
fleetInfraRepository := outputs["fleet_infra_repository"].Value.(map[string]interface{})
applicationRepository := outputs["application_repository"].Value.(map[string]interface{})
fluxAzureSp := outputs["flux_azure_sp"].Value.(map[string]interface{})
sharedSopsId := outputs["sops_id"].Value.(string)
acr := outputs["acr"].Value.(map[string]interface{})
eventHubSas := outputs["event_hub_sas"].Value.(string)
log.Println("Creating Kubernetes client")
kubeconfigPath, kubeClient, err := getKubernetesCredentials(kubeconfig, aksHost, aksCert, aksKey, aksCa)
if err != nil {
log.Fatalf("error create Kubernetes client: %v", err)
}
defer os.RemoveAll(filepath.Dir(kubeconfigPath))
cfg = config{
kubeconfigPath: kubeconfigPath,
kubeClient: kubeClient,
azdoPat: azdoPat,
idRsa: idRsa,
idRsaPub: idRsaPub,
knownHosts: "ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H",
fleetInfraRepository: repoConfig{
http: fleetInfraRepository["http"].(string),
ssh: fleetInfraRepository["ssh"].(string),
},
applicationRepository: repoConfig{
http: applicationRepository["http"].(string),
ssh: applicationRepository["ssh"].(string),
},
fluxAzureSp: spConfig{
tenantId: fluxAzureSp["tenant_id"].(string),
clientId: fluxAzureSp["client_id"].(string),
clientSecret: fluxAzureSp["client_secret"].(string),
},
sopsId: sharedSopsId,
acr: acrConfig{
url: acr["url"].(string),
username: acr["username"].(string),
password: acr["password"].(string),
},
eventHubSas: eventHubSas,
}
err = installFlux(ctx, kubeClient, kubeconfigPath, cfg.fleetInfraRepository.http, azdoPat, cfg.fluxAzureSp)
if err != nil {
log.Fatalf("error installing Flux: %v", err)
}
log.Println("Running Azure e2e tests")
exitVal := m.Run()
log.Println("Tearing down Azure test infrastructure")
err = tf.Destroy(ctx)
if err != nil {
log.Fatalf("error running Show: %v", err)
}
os.Exit(exitVal)
}
func TestFluxInstallation(t *testing.T) {
ctx := context.TODO()
require.Eventually(t, func() bool {
err := verifyGitAndKustomization(ctx, cfg.kubeClient, "flux-system", "flux-system")
if err != nil {
return false
}
return true
}, 60*time.Second, 5*time.Second)
}
func TestAzureDevOpsCloning(t *testing.T) {
ctx := context.TODO()
branchName := "feature/branch"
tagName := "v1"
tests := []struct {
name string
refType string
cloneType string
}{
{
name: "https-feature-branch",
refType: "branch",
cloneType: "http",
},
{
name: "https-v1",
refType: "tag",
cloneType: "http",
},
{
name: "ssh-feature-branch",
refType: "branch",
cloneType: "ssh",
},
{
name: "ssh-v1",
refType: "tag",
cloneType: "ssh",
},
}
t.Log("Creating application sources")
repo, repoDir, err := getRepository(cfg.applicationRepository.http, branchName, true, cfg.azdoPat)
require.NoError(t, err)
for _, tt := range tests {
manifest := fmt.Sprintf(`
apiVersion: v1
kind: ConfigMap
metadata:
name: foobar
namespace: %s
`, tt.name)
err = runCommand(ctx, repoDir, fmt.Sprintf("mkdir -p ./cloning-test/%s", tt.name))
require.NoError(t, err)
err = runCommand(ctx, repoDir, fmt.Sprintf("echo '%s' > ./cloning-test/%s/configmap.yaml", manifest, tt.name))
require.NoError(t, err)
}
err = commitAndPushAll(repo, branchName, cfg.azdoPat)
require.NoError(t, err)
err = createTagAndPush(repo, branchName, tagName, cfg.azdoPat)
require.NoError(t, err)
t.Log("Verifying application-gitops namespaces")
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref := &sourcev1.GitRepositoryRef{
Branch: branchName,
}
if tt.refType == "tag" {
ref = &sourcev1.GitRepositoryRef{
Tag: tagName,
}
}
url := cfg.applicationRepository.http
secretData := map[string]string{
"username": "git",
"password": cfg.azdoPat,
}
if tt.cloneType == "ssh" {
url = cfg.applicationRepository.ssh
secretData = map[string]string{
"identity": cfg.idRsa,
"identity.pub": cfg.idRsaPub,
"known_hosts": cfg.knownHosts,
}
}
namespace := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: tt.name,
},
}
_, err := controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &namespace, func() error {
return nil
})
gitSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "git-credentials",
Namespace: namespace.Name,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, gitSecret, func() error {
gitSecret.StringData = secretData
return nil
})
source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: tt.name, Namespace: namespace.Name}}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, source, func() error {
source.Spec = sourcev1.GitRepositorySpec{
GitImplementation: sourcev1.LibGit2Implementation,
Reference: ref,
SecretRef: &meta.LocalObjectReference{
Name: gitSecret.Name,
},
URL: url,
}
return nil
})
require.NoError(t, err)
kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: tt.name, Namespace: namespace.Name}}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error {
kustomization.Spec = kustomizev1.KustomizationSpec{
Path: fmt.Sprintf("./cloning-test/%s", tt.name),
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Kind: sourcev1.GitRepositoryKind,
Name: tt.name,
Namespace: namespace.Name,
},
Interval: metav1.Duration{Duration: 1 * time.Minute},
Prune: true,
}
return nil
})
require.NoError(t, err)
// Wait for configmap to be deployed
require.Eventually(t, func() bool {
err := verifyGitAndKustomization(ctx, cfg.kubeClient, namespace.Name, tt.name)
if err != nil {
return false
}
nn := types.NamespacedName{Name: "foobar", Namespace: namespace.Name}
cm := &corev1.ConfigMap{}
err = cfg.kubeClient.Get(ctx, nn, cm)
if err != nil {
return false
}
return true
}, 120*time.Second, 5*time.Second)
})
}
}
func TestImageRepositoryACR(t *testing.T) {
ctx := context.TODO()
name := "image-repository-acr"
repoUrl := cfg.applicationRepository.http
oldVersion := "1.0.0"
newVersion := "1.0.1"
manifest := fmt.Sprintf(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: podinfo
namespace: %s
spec:
selector:
matchLabels:
app: podinfo
template:
metadata:
labels:
app: podinfo
spec:
containers:
- name: podinfod
image: %s/container/podinfo:%s # {"$imagepolicy": "%s:podinfo"}
readinessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/readyz
initialDelaySeconds: 5
timeoutSeconds: 5`, name, cfg.acr.url, oldVersion, name)
repo, repoDir, err := getRepository(repoUrl, name, true, cfg.azdoPat)
require.NoError(t, err)
err = addFile(repoDir, "podinfo.yaml", manifest)
require.NoError(t, err)
err = commitAndPushAll(repo, name, cfg.azdoPat)
require.NoError(t, err)
err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name)
require.NoError(t, err)
acrSecret := corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "acr-docker", Namespace: name}}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &acrSecret, func() error {
acrSecret.Type = corev1.SecretTypeDockerConfigJson
acrSecret.StringData = map[string]string{
".dockerconfigjson": fmt.Sprintf(`
{
"auths": {
"%s": {
"auth": "%s"
}
}
}
`, cfg.acr.url, b64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", cfg.acr.username, cfg.acr.password)))),
}
return nil
})
require.NoError(t, err)
imageRepository := reflectorv1beta1.ImageRepository{
ObjectMeta: metav1.ObjectMeta{
Name: "podinfo",
Namespace: name,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &imageRepository, func() error {
imageRepository.Spec = reflectorv1beta1.ImageRepositorySpec{
Image: fmt.Sprintf("%s/container/podinfo", cfg.acr.url),
Interval: metav1.Duration{
Duration: 1 * time.Minute,
},
SecretRef: &meta.LocalObjectReference{
Name: acrSecret.Name,
},
}
return nil
})
require.NoError(t, err)
imagePolicy := reflectorv1beta1.ImagePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "podinfo",
Namespace: name,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &imagePolicy, func() error {
imagePolicy.Spec = reflectorv1beta1.ImagePolicySpec{
ImageRepositoryRef: meta.NamespacedObjectReference{
Name: imageRepository.Name,
},
Policy: reflectorv1beta1.ImagePolicyChoice{
SemVer: &reflectorv1beta1.SemVerPolicy{
Range: "1.0.x",
},
},
}
return nil
})
require.NoError(t, err)
imageAutomation := automationv1beta1.ImageUpdateAutomation{
ObjectMeta: metav1.ObjectMeta{
Name: "podinfo",
Namespace: name,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &imageAutomation, func() error {
imageAutomation.Spec = automationv1beta1.ImageUpdateAutomationSpec{
Interval: metav1.Duration{
Duration: 1 * time.Minute,
},
SourceRef: automationv1beta1.SourceReference{
Kind: "GitRepository",
Name: name,
},
GitSpec: &automationv1beta1.GitSpec{
Checkout: &automationv1beta1.GitCheckoutSpec{
Reference: sourcev1.GitRepositoryRef{
Branch: name,
},
},
Commit: automationv1beta1.CommitSpec{
Author: automationv1beta1.CommitUser{
Email: "imageautomation@example.com",
Name: "imageautomation",
},
},
},
}
return nil
})
require.NoError(t, err)
// Wait for image repository to be ready
require.Eventually(t, func() bool {
_, repoDir, err := getRepository(repoUrl, name, false, cfg.azdoPat)
if err != nil {
return false
}
b, err := os.ReadFile(filepath.Join(repoDir, "podinfo.yaml"))
if err != nil {
return false
}
if bytes.Contains(b, []byte(newVersion)) == false {
return false
}
return true
}, 120*time.Second, 5*time.Second)
}
func TestKeyVaultSops(t *testing.T) {
ctx := context.TODO()
name := "key-vault-sops"
repoUrl := cfg.applicationRepository.http
secretYaml := `apiVersion: v1
kind: Secret
metadata:
name: "test"
namespace: "key-vault-sops"
stringData:
foo: "bar"`
repo, tmpDir, err := getRepository(repoUrl, name, true, cfg.azdoPat)
err = runCommand(ctx, tmpDir, "mkdir -p ./key-vault-sops")
require.NoError(t, err)
err = runCommand(ctx, tmpDir, fmt.Sprintf("echo \"%s\" > ./key-vault-sops/secret.enc.yaml", secretYaml))
require.NoError(t, err)
err = runCommand(ctx, tmpDir, fmt.Sprintf("sops --encrypt --encrypted-regex '^(data|stringData)$' --azure-kv %s --in-place ./key-vault-sops/secret.enc.yaml", cfg.sopsId))
require.NoError(t, err)
err = commitAndPushAll(repo, name, cfg.azdoPat)
require.NoError(t, err)
err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name)
require.NoError(t, err)
source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}
require.Eventually(t, func() bool {
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, source, func() error {
source.Spec = sourcev1.GitRepositorySpec{
GitImplementation: sourcev1.LibGit2Implementation,
Reference: &sourcev1.GitRepositoryRef{
Branch: name,
},
SecretRef: &meta.LocalObjectReference{
Name: "https-credentials",
},
URL: repoUrl,
}
return nil
})
if err != nil {
return false
}
return true
}, 10*time.Second, 1*time.Second)
kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}
require.Eventually(t, func() bool {
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error {
kustomization.Spec = kustomizev1.KustomizationSpec{
Path: "./key-vault-sops",
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Kind: sourcev1.GitRepositoryKind,
Name: source.Name,
Namespace: source.Namespace,
},
Interval: metav1.Duration{Duration: 1 * time.Minute},
Prune: true,
Decryption: &kustomizev1.Decryption{
Provider: "sops",
},
}
return nil
})
if err != nil {
return false
}
return true
}, 10*time.Second, 1*time.Second)
require.Eventually(t, func() bool {
nn := types.NamespacedName{Name: "test", Namespace: name}
secret := &corev1.Secret{}
err = cfg.kubeClient.Get(ctx, nn, secret)
if err != nil {
return false
}
return true
}, 120*time.Second, 5*time.Second)
}
func TestAzureDevOpsCommitStatus(t *testing.T) {
ctx := context.TODO()
name := "commit-status"
repoUrl := cfg.applicationRepository.http
manifest := fmt.Sprintf(`
apiVersion: v1
kind: ConfigMap
metadata:
name: foobar
namespace: %s
`, name)
repo, repoDir, err := getRepository(repoUrl, name, true, cfg.azdoPat)
require.NoError(t, err)
err = addFile(repoDir, "configmap.yaml", manifest)
require.NoError(t, err)
err = commitAndPushAll(repo, name, cfg.azdoPat)
require.NoError(t, err)
err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name)
require.NoError(t, err)
kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}
require.Eventually(t, func() bool {
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error {
kustomization.Spec.HealthChecks = []meta.NamespacedObjectKindReference{
{
APIVersion: "v1",
Kind: "ConfigMap",
Name: "foobar",
Namespace: name,
},
}
return nil
})
if err != nil {
return false
}
return true
}, 10*time.Second, 1*time.Second)
require.Eventually(t, func() bool {
err := verifyGitAndKustomization(ctx, cfg.kubeClient, name, name)
if err != nil {
return false
}
return true
}, 10*time.Second, 1*time.Second)
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "azuredevops-token",
Namespace: name,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &secret, func() error {
secret.StringData = map[string]string{
"token": cfg.azdoPat,
}
return nil
})
provider := notiv1beta1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "azuredevops",
Namespace: name,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &provider, func() error {
provider.Spec = notiv1beta1.ProviderSpec{
Type: "azuredevops",
Address: repoUrl,
SecretRef: &meta.LocalObjectReference{
Name: "azuredevops-token",
},
}
return nil
})
require.NoError(t, err)
alert := notiv1beta1.Alert{
ObjectMeta: metav1.ObjectMeta{
Name: "azuredevops",
Namespace: name,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &alert, func() error {
alert.Spec = notiv1beta1.AlertSpec{
ProviderRef: meta.LocalObjectReference{
Name: provider.Name,
},
EventSources: []notiv1beta1.CrossNamespaceObjectReference{
{
Kind: "Kustomization",
Name: name,
Namespace: name,
},
},
}
return nil
})
require.NoError(t, err)
u, err := giturls.Parse(repoUrl)
require.NoError(t, err)
id := strings.TrimLeft(u.Path, "/")
id = strings.TrimSuffix(id, ".git")
comp := strings.Split(id, "/")
orgUrl := fmt.Sprintf("%s://%s/%v", u.Scheme, u.Host, comp[0])
project := comp[1]
repoId := comp[3]
branch, err := repo.LookupBranch(name, git2go.BranchLocal)
require.NoError(t, err)
commit, err := repo.LookupCommit(branch.Target())
rev := commit.Id().String()
connection := azuredevops.NewPatConnection(orgUrl, cfg.azdoPat)
client, err := git.NewClient(ctx, connection)
require.NoError(t, err)
getArgs := git.GetStatusesArgs{
Project: &project,
RepositoryId: &repoId,
CommitId: &rev,
}
require.Eventually(t, func() bool {
statuses, err := client.GetStatuses(ctx, getArgs)
if err != nil {
return false
}
if len(*statuses) != 1 {
return false
}
return true
}, 500*time.Second, 5*time.Second)
}
func TestEventHubNotification(t *testing.T) {
ctx := context.TODO()
name := "event-hub"
// Start listening to eventhub with latest offset
hub, err := eventhub.NewHubFromConnectionString(cfg.eventHubSas)
require.NoError(t, err)
c := make(chan string, 10)
handler := func(ctx context.Context, event *eventhub.Event) error {
c <- string(event.Data)
return nil
}
runtimeInfo, err := hub.GetRuntimeInformation(ctx)
require.NoError(t, err)
require.Equal(t, 1, len(runtimeInfo.PartitionIDs))
listenerHandler, err := hub.Receive(ctx, runtimeInfo.PartitionIDs[0], handler, eventhub.ReceiveWithLatestOffset())
require.NoError(t, err)
// Setup Flux resources
repoUrl := cfg.applicationRepository.http
manifest := fmt.Sprintf(`
apiVersion: v1
kind: ConfigMap
metadata:
name: foobar
namespace: %s
`, name)
repo, repoDir, err := getRepository(repoUrl, name, true, cfg.azdoPat)
require.NoError(t, err)
err = addFile(repoDir, "configmap.yaml", manifest)
require.NoError(t, err)
err = commitAndPushAll(repo, name, cfg.azdoPat)
require.NoError(t, err)
err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name)
require.NoError(t, err)
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: name,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &secret, func() error {
secret.StringData = map[string]string{
"address": cfg.eventHubSas,
}
return nil
})
provider := notiv1beta1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: name,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &provider, func() error {
provider.Spec = notiv1beta1.ProviderSpec{
Type: "azureeventhub",
Address: repoUrl,
SecretRef: &meta.LocalObjectReference{
Name: name,
},
}
return nil
})
require.NoError(t, err)
alert := notiv1beta1.Alert{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: name,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &alert, func() error {
alert.Spec = notiv1beta1.AlertSpec{
ProviderRef: meta.LocalObjectReference{
Name: provider.Name,
},
EventSources: []notiv1beta1.CrossNamespaceObjectReference{
{
Kind: "Kustomization",
Name: name,
Namespace: name,
},
},
}
return nil
})
require.NoError(t, err)
kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}
require.Eventually(t, func() bool {
_, err := controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error {
kustomization.Spec.HealthChecks = []meta.NamespacedObjectKindReference{
{
APIVersion: "v1",
Kind: "ConfigMap",
Name: "foobar",
Namespace: name,
},
}
return nil
})
if err != nil {
return false
}
return true
}, 10*time.Second, 1*time.Second)
require.Eventually(t, func() bool {
err := verifyGitAndKustomization(ctx, cfg.kubeClient, name, name)
if err != nil {
return false
}
return true
}, 60*time.Second, 5*time.Second)
// Wait to read even from event hub
require.Eventually(t, func() bool {
select {
case eventJson := <-c:
event := &events.Event{}
err := json.Unmarshal([]byte(eventJson), event)
if err != nil {
t.Logf("the received event type does not match Flux format, error: %v", err)
return false
}
if event.InvolvedObject.Kind == kustomizev1.KustomizationKind &&
strings.Contains(event.Message, "Health check passed") {
return true
}
t.Logf("event received from '%s/%s': %s",
event.InvolvedObject.Kind, event.InvolvedObject.Name, event.Message)
return false
default:
return false
}
}, 60*time.Second, 1*time.Second)
err = listenerHandler.Close(ctx)
require.NoError(t, err)
err = hub.Close(ctx)
require.NoError(t, err)
}
// TODO: Enable when source-controller supports Helm charts from OCI sources.
/*func TestACRHelmRelease(t *testing.T) {
ctx := context.TODO()
// Create namespace for test
namespace := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "acr-helm-release",
},
}
err := kubeClient.Create(ctx, &namespace)
require.NoError(t, err)
defer func() {
kubeClient.Delete(ctx, &namespace)
require.NoError(t, err)
}()
// Copy ACR credentials to new namespace
acrNn := types.NamespacedName{
Name: "acr-helm",
Namespace: "flux-system",
}
acrSecret := corev1.Secret{}
err = kubeClient.Get(ctx, acrNn, &acrSecret)
require.NoError(t, err)
acrSecret.ObjectMeta = metav1.ObjectMeta{
Name: acrSecret.Name,
Namespace: namespace.Name,
}
err = kubeClient.Create(ctx, &acrSecret)
require.NoError(t, err)
// Create HelmRepository and wait for it to sync
helmRepository := sourcev1.HelmRepository{
ObjectMeta: metav1.ObjectMeta{
Name: "acr",
Namespace: namespace.Name,
},
Spec: sourcev1.HelmRepositorySpec{
URL: "https://acrappsoarfish.azurecr.io/helm/podinfo",
Interval: metav1.Duration{
Duration: 5 * time.Minute,
},
SecretRef: &meta.LocalObjectReference{
Name: acrSecret.Name,
},
PassCredentials: true,
},
}
err = kubeClient.Create(ctx, &helmRepository)
require.NoError(t, err)
}*/

@ -0,0 +1,24 @@
module github.com/fluxcd/flux2/tests/azure
go 1.16
require (
github.com/Azure/azure-event-hubs-go/v3 v3.3.13
github.com/fluxcd/helm-controller/api v0.12.0
github.com/fluxcd/image-automation-controller/api v0.15.0
github.com/fluxcd/image-reflector-controller/api v0.12.0
github.com/fluxcd/kustomize-controller/api v0.15.2
github.com/fluxcd/notification-controller/api v0.17.0
github.com/fluxcd/pkg/apis/meta v0.10.1
github.com/fluxcd/pkg/runtime v0.12.1
github.com/fluxcd/source-controller/api v0.16.0
github.com/hashicorp/terraform-exec v0.14.0
github.com/libgit2/git2go/v31 v31.6.1
github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5
github.com/stretchr/testify v1.7.0
github.com/whilp/git-urls v1.0.0
k8s.io/api v0.22.2
k8s.io/apimachinery v0.22.2
k8s.io/client-go v0.22.2
sigs.k8s.io/controller-runtime v0.10.1
)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,19 @@
package test
import (
"context"
"os/exec"
"strings"
)
type whichTerraform struct{}
func (w *whichTerraform) ExecPath(ctx context.Context) (string, error) {
cmd := exec.CommandContext(ctx, "which", "terraform")
output, err := cmd.Output()
if err != nil {
return "", err
}
path := strings.TrimSuffix(string(output), "\n")
return path, nil
}

@ -0,0 +1,78 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/azuread" {
version = "1.6.0"
constraints = "1.6.0"
hashes = [
"h1:BlO53mX+Y2W//YqlCKvoxzofegFQk636XlKtmZYH0PY=",
"zh:0db70045a464d325fdb3d71809f0467844c3e2fcf1349e568bc51ad5035c99d9",
"zh:3629f1d7b4eba48d744b24c7cf7fe878d5ef5910a36b525507bd3d588010ccec",
"zh:5a73a45b6d1ff353810cc9b00d7c90a2fb328ba0a9ef3d24392b1500fb98741a",
"zh:7a6a9c390cf1bf752321abb8d0643c9f623e8c2ad871dfb378d64c9d90fada2d",
"zh:7d6de55d326b046dabc16bd7b655f008ff780c36ffc884b139a7c7da37b446d5",
"zh:8d725c618396ccae290e411296c892e08e776c3e9e5a82b0ef1f633a917146ec",
"zh:a206d1d8042bf66ca12b97334bbd6fcdf12fd6131f8cb4547c82b9fa7a701612",
"zh:b03ab4ff07dcb5ed8be8b0619c6ec9fb0da0c83594ccb0a1bff72f346083b530",
"zh:b6131f9d438b340a4016c770b569139ec7ac2532358a8ab783234e8c93d141d5",
"zh:ce9372d38e9e62accfd54f4669753000d3dcbae4b45686d74630eb63eb879f37",
"zh:df9a607c333d464d8bdeb248b1ff41e493c1d0661453a1e1ce396b89952a74ee",
]
}
provider "registry.terraform.io/hashicorp/azurerm" {
version = "2.76.0"
constraints = "2.76.0"
hashes = [
"h1:kF+u0s0DPnE5gMKhzQACWRUIdwZG1Ax4atXt9hk1J8M=",
"zh:137eb7c07d3d3c9fe123e74381c108c4442efba9fc051faa2ca603503ff2840f",
"zh:142a354dffd59a1d6b7f1614ab66a468ace3636d95933589a8d704ee8dbc4ea6",
"zh:4c343b4da8b86e4213c1b11f73337cec73a55b1fa95a0e0e0c79f34597d37cc3",
"zh:75d3109d48726fdbaad840d2fa294ec3362b32a3628c261af00f5c5608427521",
"zh:7b1e78c144c6ad2beebc798abb9e76c725bf34ced41df36dc0120a0f2426e801",
"zh:981235b01c3d4acf94c78cdd96624fd01d0a3622bc06b5c62aef3e788f1481c3",
"zh:bad819efae7293ce371409e1ed34197c3e879f61d3e44893af0ce68e6aaffde7",
"zh:c8008967722929deccfec9695754ae55028ce12311c321ae7a7c753dde162a44",
"zh:d38513d1138864269b2ff333b08a64a7949630d489f18e660630bbaff3b7ebb8",
"zh:e1f64d2d91b5f5cba6a9c5d35278a4918d332d7385a87f8e3466aaadb782a90f",
"zh:e93a377a1e823df69718686703b07f1712046eeb742006022e982f2e8a594161",
]
}
provider "registry.terraform.io/hashicorp/random" {
version = "3.1.0"
hashes = [
"h1:BZMEPucF+pbu9gsPk0G0BHx7YP04+tKdq2MrRDF1EDM=",
"zh:2bbb3339f0643b5daa07480ef4397bd23a79963cc364cdfbb4e86354cb7725bc",
"zh:3cd456047805bf639fbf2c761b1848880ea703a054f76db51852008b11008626",
"zh:4f251b0eda5bb5e3dc26ea4400dba200018213654b69b4a5f96abee815b4f5ff",
"zh:7011332745ea061e517fe1319bd6c75054a314155cb2c1199a5b01fe1889a7e2",
"zh:738ed82858317ccc246691c8b85995bc125ac3b4143043219bd0437adc56c992",
"zh:7dbe52fac7bb21227acd7529b487511c91f4107db9cc4414f50d04ffc3cab427",
"zh:a3a9251fb15f93e4cfc1789800fc2d7414bbc18944ad4c5c98f466e6477c42bc",
"zh:a543ec1a3a8c20635cf374110bd2f87c07374cf2c50617eee2c669b3ceeeaa9f",
"zh:d9ab41d556a48bd7059f0810cf020500635bfc696c9fc3adab5ea8915c1d886b",
"zh:d9e13427a7d011dbd654e591b0337e6074eef8c3b9bb11b2e39eaaf257044fd7",
"zh:f7605bd1437752114baf601bdf6931debe6dc6bfe3006eb7e9bb9080931dca8a",
]
}
provider "registry.terraform.io/microsoft/azuredevops" {
version = "0.1.7"
constraints = "0.1.7"
hashes = [
"h1:AWNWqJ3XhlKp3xdJF+3WKdK1zVoCFYInQvi06exsBzg=",
"zh:0c024992f2282ef73d4829e487ec8482dd98e9272b903f2e5979f5f62567ee4e",
"zh:47fef8f57dfdca6aebe5a907b4866880007512019d9bec29805fc83501412309",
"zh:692736c501c6b987a4a74c69fb7702a54969180706d1f67eff13e6ed2a0f9fec",
"zh:6c3c4339206f5dcbc9d10fb2fe343652e7e14255223dcece5bf79ef9030858ef",
"zh:77dfc63377b8d8fe24cbbe479ead18bfd1c7ded067fd694b6532434d6305ad31",
"zh:93dba26dbade208a1cba43333f104a64252ca2404636ab033702da29648bfaaa",
"zh:952d28b3e6c137de9b8700d2b748e5a4a2aa53ed07005f0f7abdd66b84cc63fe",
"zh:a7b8238b8b2f04ad2d720a207377bfc2066d54b1d9d7285f2535afc43ff80fdb",
"zh:bb23d8fc3cdd3c01d7620dadb2ba7b724706f2112d7738e135d1be1455682f5e",
"zh:cb4da640beb5fc59296479c201a03351789496c04aaa57ae1530a7aac9095b92",
"zh:ede6fb7ab598081fdddac56d470bae14448271dfd43a645bc02d136643391ebe",
"zh:fd8291e6dc9118323a744660326a0f11de2a475c4a358e50f480feed1f3bb080",
]
}

@ -0,0 +1,37 @@
resource "azurerm_kubernetes_cluster" "this" {
name = "aks-${local.name_suffix}"
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
dns_prefix = "aks${local.name_suffix}"
default_node_pool {
name = "default"
node_count = 2
vm_size = "Standard_B2s"
os_disk_size_gb = 30
}
identity {
type = "SystemAssigned"
}
role_based_access_control {
enabled = true
}
network_profile {
network_plugin = "kubenet"
network_policy = "calico"
}
tags = {
environment = "e2e"
}
}
resource "azurerm_role_assignment" "aks_acr_pull" {
scope = data.azurerm_container_registry.shared.id
role_definition_name = "AcrPull"
principal_id = azurerm_kubernetes_cluster.this.kubelet_identity[0].object_id
}

@ -0,0 +1,21 @@
data "azuredevops_project" "e2e" {
name = "e2e"
}
resource "azuredevops_git_repository" "fleet_infra" {
project_id = data.azuredevops_project.e2e.id
name = "fleet-infra-${local.name_suffix}"
default_branch = "refs/heads/main"
initialization {
init_type = "Clean"
}
}
resource "azuredevops_git_repository" "application" {
project_id = data.azuredevops_project.e2e.id
name = "application-${local.name_suffix}"
default_branch = "refs/heads/main"
initialization {
init_type = "Clean"
}
}

@ -0,0 +1,26 @@
resource "azurerm_eventhub_namespace" "this" {
name = "ehns-${local.name_suffix}"
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
sku = "Standard"
capacity = 1
}
resource "azurerm_eventhub" "this" {
name = "eh-${local.name_suffix}"
namespace_name = azurerm_eventhub_namespace.this.name
resource_group_name = azurerm_resource_group.this.name
partition_count = 1
message_retention = 1
}
resource "azurerm_eventhub_authorization_rule" "this" {
name = "flux"
resource_group_name = azurerm_resource_group.this.name
namespace_name = azurerm_eventhub_namespace.this.name
eventhub_name = azurerm_eventhub.this.name
listen = true
send = true
manage = false
}

@ -0,0 +1,37 @@
resource "azurerm_key_vault" "this" {
name = "kv-${random_pet.suffix.id}"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
}
resource "azurerm_key_vault_access_policy" "sops_write" {
key_vault_id = azurerm_key_vault.this.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
key_permissions = [
"Encrypt",
"Decrypt",
"Create",
"Delete",
"Purge",
"Get",
"List",
]
}
resource "azurerm_key_vault_key" "sops" {
depends_on = [azurerm_key_vault_access_policy.sops_write]
name = "sops"
key_vault_id = azurerm_key_vault.this.id
key_type = "RSA"
key_size = 2048
key_opts = [
"decrypt",
"encrypt",
]
}

@ -0,0 +1,52 @@
terraform {
backend "azurerm" {
resource_group_name = "terraform-state"
storage_account_name = "terraformstate0419"
container_name = "aks-tfstate"
key = "prod.terraform.tfstate"
}
required_version = "1.0.7"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "2.76.0"
}
azuread = {
source = "hashicorp/azuread"
version = "1.6.0"
}
azuredevops = {
source = "microsoft/azuredevops"
version = "0.1.7"
}
}
}
provider "azurerm" {
features {}
}
provider "azuredevops" {
org_service_url = "https://dev.azure.com/${local.azure_devops_org}"
personal_access_token = data.azurerm_key_vault_secret.shared_pat.value
}
data "azurerm_client_config" "current" {}
resource "random_pet" "suffix" {}
locals {
azure_devops_org = "flux-azure"
name_suffix = "e2e-${random_pet.suffix.id}"
}
resource "azurerm_resource_group" "this" {
name = "rg-${local.name_suffix}"
location = "West Europe"
tags = {
environment = "e2e"
}
}

@ -0,0 +1,76 @@
output "aks_kube_config" {
sensitive = true
value = azurerm_kubernetes_cluster.this.kube_config_raw
}
output "aks_host" {
value = azurerm_kubernetes_cluster.this.kube_config[0].host
}
output "aks_client_certificate" {
value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].client_certificate)
}
output "aks_client_key" {
value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].client_key)
}
output "aks_cluster_ca_certificate" {
value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].cluster_ca_certificate)
}
output "shared_pat" {
sensitive = true
value = data.azurerm_key_vault_secret.shared_pat.value
}
output "shared_id_rsa" {
sensitive = true
value = data.azurerm_key_vault_secret.shared_id_rsa.value
}
output "shared_id_rsa_pub" {
sensitive = true
value = data.azurerm_key_vault_secret.shared_id_rsa_pub.value
}
output "fleet_infra_repository" {
value = {
http = azuredevops_git_repository.fleet_infra.remote_url
ssh = "ssh://git@ssh.dev.azure.com/v3/${local.azure_devops_org}/${azuredevops_git_repository.fleet_infra.project_id}/${azuredevops_git_repository.fleet_infra.name}"
}
}
output "application_repository" {
value = {
http = azuredevops_git_repository.application.remote_url
ssh = "ssh://git@ssh.dev.azure.com/v3/${local.azure_devops_org}/${azuredevops_git_repository.application.project_id}/${azuredevops_git_repository.application.name}"
}
}
output "flux_azure_sp" {
value = {
tenant_id = data.azurerm_client_config.current.tenant_id
client_id = azuread_service_principal.flux.application_id
client_secret = azuread_service_principal_password.flux.value
}
sensitive = true
}
output "event_hub_sas" {
value = azurerm_eventhub_authorization_rule.this.primary_connection_string
sensitive = true
}
output "sops_id" {
value = azurerm_key_vault_key.sops.id
}
output "acr" {
value = {
url = data.azurerm_container_registry.shared.login_server
username = azuread_service_principal.flux.application_id
password = azuread_service_principal_password.flux.value
}
sensitive = true
}

@ -0,0 +1,52 @@
resource "azuread_application" "flux" {
display_name = "flux-${local.name_suffix}"
required_resource_access {
resource_app_id = "00000003-0000-0000-c000-000000000000"
resource_access {
id = "df021288-bdef-4463-88db-98f22de89214"
type = "Role"
}
}
required_resource_access {
resource_app_id = "00000002-0000-0000-c000-000000000000"
resource_access {
id = "1cda74f2-2616-4834-b122-5cb1b07f8a59"
type = "Role"
}
resource_access {
id = "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175"
type = "Role"
}
}
}
resource "azuread_service_principal" "flux" {
application_id = azuread_application.flux.application_id
}
resource "azuread_service_principal_password" "flux" {
service_principal_id = azuread_service_principal.flux.object_id
}
resource "azurerm_role_assignment" "acr" {
scope = data.azurerm_container_registry.shared.id
role_definition_name = "AcrPull"
principal_id = azuread_service_principal.flux.object_id
}
resource "azurerm_key_vault_access_policy" "sops_decrypt" {
key_vault_id = azurerm_key_vault.this.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = azuread_service_principal.flux.object_id
key_permissions = [
"Encrypt",
"Decrypt",
"Get",
"List",
]
}

@ -0,0 +1,32 @@
locals {
shared_suffix = "oarfish"
}
data "azurerm_resource_group" "shared" {
name = "e2e-shared"
}
data "azurerm_container_registry" "shared" {
name = "acrapps${local.shared_suffix}"
resource_group_name = data.azurerm_resource_group.shared.name
}
data "azurerm_key_vault" "shared" {
resource_group_name = data.azurerm_resource_group.shared.name
name = "kv-credentials-${local.shared_suffix}"
}
data "azurerm_key_vault_secret" "shared_pat" {
key_vault_id = data.azurerm_key_vault.shared.id
name = "pat"
}
data "azurerm_key_vault_secret" "shared_id_rsa" {
key_vault_id = data.azurerm_key_vault.shared.id
name = "id-rsa"
}
data "azurerm_key_vault_secret" "shared_id_rsa_pub" {
key_vault_id = data.azurerm_key_vault.shared.id
name = "id-rsa-pub"
}

@ -0,0 +1,58 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/azuread" {
version = "1.6.0"
constraints = "1.6.0"
hashes = [
"h1:BlO53mX+Y2W//YqlCKvoxzofegFQk636XlKtmZYH0PY=",
"zh:0db70045a464d325fdb3d71809f0467844c3e2fcf1349e568bc51ad5035c99d9",
"zh:3629f1d7b4eba48d744b24c7cf7fe878d5ef5910a36b525507bd3d588010ccec",
"zh:5a73a45b6d1ff353810cc9b00d7c90a2fb328ba0a9ef3d24392b1500fb98741a",
"zh:7a6a9c390cf1bf752321abb8d0643c9f623e8c2ad871dfb378d64c9d90fada2d",
"zh:7d6de55d326b046dabc16bd7b655f008ff780c36ffc884b139a7c7da37b446d5",
"zh:8d725c618396ccae290e411296c892e08e776c3e9e5a82b0ef1f633a917146ec",
"zh:a206d1d8042bf66ca12b97334bbd6fcdf12fd6131f8cb4547c82b9fa7a701612",
"zh:b03ab4ff07dcb5ed8be8b0619c6ec9fb0da0c83594ccb0a1bff72f346083b530",
"zh:b6131f9d438b340a4016c770b569139ec7ac2532358a8ab783234e8c93d141d5",
"zh:ce9372d38e9e62accfd54f4669753000d3dcbae4b45686d74630eb63eb879f37",
"zh:df9a607c333d464d8bdeb248b1ff41e493c1d0661453a1e1ce396b89952a74ee",
]
}
provider "registry.terraform.io/hashicorp/azurerm" {
version = "2.76.0"
constraints = "2.76.0"
hashes = [
"h1:kF+u0s0DPnE5gMKhzQACWRUIdwZG1Ax4atXt9hk1J8M=",
"zh:137eb7c07d3d3c9fe123e74381c108c4442efba9fc051faa2ca603503ff2840f",
"zh:142a354dffd59a1d6b7f1614ab66a468ace3636d95933589a8d704ee8dbc4ea6",
"zh:4c343b4da8b86e4213c1b11f73337cec73a55b1fa95a0e0e0c79f34597d37cc3",
"zh:75d3109d48726fdbaad840d2fa294ec3362b32a3628c261af00f5c5608427521",
"zh:7b1e78c144c6ad2beebc798abb9e76c725bf34ced41df36dc0120a0f2426e801",
"zh:981235b01c3d4acf94c78cdd96624fd01d0a3622bc06b5c62aef3e788f1481c3",
"zh:bad819efae7293ce371409e1ed34197c3e879f61d3e44893af0ce68e6aaffde7",
"zh:c8008967722929deccfec9695754ae55028ce12311c321ae7a7c753dde162a44",
"zh:d38513d1138864269b2ff333b08a64a7949630d489f18e660630bbaff3b7ebb8",
"zh:e1f64d2d91b5f5cba6a9c5d35278a4918d332d7385a87f8e3466aaadb782a90f",
"zh:e93a377a1e823df69718686703b07f1712046eeb742006022e982f2e8a594161",
]
}
provider "registry.terraform.io/hashicorp/random" {
version = "3.1.0"
hashes = [
"h1:BZMEPucF+pbu9gsPk0G0BHx7YP04+tKdq2MrRDF1EDM=",
"zh:2bbb3339f0643b5daa07480ef4397bd23a79963cc364cdfbb4e86354cb7725bc",
"zh:3cd456047805bf639fbf2c761b1848880ea703a054f76db51852008b11008626",
"zh:4f251b0eda5bb5e3dc26ea4400dba200018213654b69b4a5f96abee815b4f5ff",
"zh:7011332745ea061e517fe1319bd6c75054a314155cb2c1199a5b01fe1889a7e2",
"zh:738ed82858317ccc246691c8b85995bc125ac3b4143043219bd0437adc56c992",
"zh:7dbe52fac7bb21227acd7529b487511c91f4107db9cc4414f50d04ffc3cab427",
"zh:a3a9251fb15f93e4cfc1789800fc2d7414bbc18944ad4c5c98f466e6477c42bc",
"zh:a543ec1a3a8c20635cf374110bd2f87c07374cf2c50617eee2c669b3ceeeaa9f",
"zh:d9ab41d556a48bd7059f0810cf020500635bfc696c9fc3adab5ea8915c1d886b",
"zh:d9e13427a7d011dbd654e591b0337e6074eef8c3b9bb11b2e39eaaf257044fd7",
"zh:f7605bd1437752114baf601bdf6931debe6dc6bfe3006eb7e9bb9080931dca8a",
]
}

@ -0,0 +1,6 @@
resource "azurerm_container_registry" "this" {
name = "acrapps${random_pet.suffix.id}"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
sku = "standard"
}

@ -0,0 +1,43 @@
resource "azurerm_key_vault" "this" {
name = "kv-credentials-${random_pet.suffix.id}"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
}
resource "azurerm_key_vault_access_policy" "admin" {
key_vault_id = azurerm_key_vault.this.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
key_permissions = [
"Backup",
"Create",
"Decrypt",
"Delete",
"Encrypt",
"Get",
"Import",
"List",
"Purge",
"Recover",
"Restore",
"Sign",
"UnwrapKey",
"Update",
"Verify",
"WrapKey",
]
secret_permissions = [
"Backup",
"Delete",
"Get",
"List",
"Purge",
"Recover",
"Restore",
"Set",
]
}

@ -0,0 +1,39 @@
terraform {
backend "azurerm" {
resource_group_name = "terraform-state"
storage_account_name = "terraformstate0419"
container_name = "shared-tfstate"
key = "prod.terraform.tfstate"
}
required_version = "1.0.7"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "2.76.0"
}
azuread = {
source = "hashicorp/azuread"
version = "1.6.0"
}
}
}
provider "azurerm" {
features {}
}
resource "random_pet" "suffix" {
length = 1
separator = ""
}
data "azurerm_client_config" "current" {}
data "azurerm_subscription" "current" {}
resource "azurerm_resource_group" "this" {
name = "e2e-shared"
location = "West Europe"
}

@ -0,0 +1,18 @@
output "azure_devops_sp" {
value = {
client_id = azuread_service_principal.azure_devops.application_id
client_secret = azuread_application_password.azure_devops.value
}
sensitive = true
}
output "github_sp" {
value = {
tenant_id = data.azurerm_client_config.current.tenant_id
subscription_id = data.azurerm_client_config.current.subscription_id
client_id = azuread_service_principal.github.application_id
client_secret = azuread_application_password.github.value
}
sensitive = true
}

@ -0,0 +1,105 @@
# Service Principal used by Azure DevOps to push OCI and Helm Charts
resource "azuread_application" "azure_devops" {
display_name = "azure-devops-${random_pet.suffix.id}"
required_resource_access {
resource_app_id = "00000003-0000-0000-c000-000000000000"
resource_access {
id = "df021288-bdef-4463-88db-98f22de89214"
type = "Role"
}
}
required_resource_access {
resource_app_id = "00000002-0000-0000-c000-000000000000"
resource_access {
id = "1cda74f2-2616-4834-b122-5cb1b07f8a59"
type = "Role"
}
resource_access {
id = "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175"
type = "Role"
}
}
}
resource "azuread_application_password" "azure_devops" {
display_name = "password"
application_object_id = azuread_application.azure_devops.object_id
}
resource "azuread_service_principal" "azure_devops" {
application_id = azuread_application.azure_devops.application_id
}
resource "azurerm_role_assignment" "azure_devops_acr" {
scope = azurerm_container_registry.this.id
role_definition_name = "Contributor"
principal_id = azuread_service_principal.azure_devops.object_id
}
# Service Principal that is used to run the tests in GitHub Actions
resource "azuread_application" "github" {
display_name = "github-${random_pet.suffix.id}"
required_resource_access {
resource_app_id = "00000003-0000-0000-c000-000000000000"
resource_access {
id = "df021288-bdef-4463-88db-98f22de89214"
type = "Role"
}
}
required_resource_access {
resource_app_id = "00000002-0000-0000-c000-000000000000"
resource_access {
id = "1cda74f2-2616-4834-b122-5cb1b07f8a59"
type = "Role"
}
resource_access {
id = "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175"
type = "Role"
}
}
}
resource "azuread_application_password" "github" {
display_name = "password"
application_object_id = azuread_application.github.object_id
}
resource "azuread_service_principal" "github" {
application_id = azuread_application.github.application_id
}
data "azurerm_storage_account" "terraform_state" {
resource_group_name = "terraform-state"
name = "terraformstate0419"
}
resource "azurerm_role_assignment" "github_resource_group" {
scope = data.azurerm_subscription.current.id
role_definition_name = "Contributor"
principal_id = azuread_service_principal.github.object_id
}
resource "azurerm_role_assignment" "github_acr" {
scope = azurerm_container_registry.this.id
role_definition_name = "Owner"
principal_id = azuread_service_principal.github.object_id
}
resource "azurerm_key_vault_access_policy" "github_keyvault_secret_read" {
key_vault_id = azurerm_key_vault.this.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = azuread_service_principal.github.object_id
secret_permissions = [
"Get",
"List",
]
}

@ -0,0 +1,461 @@
package test
import (
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"time"
git2go "github.com/libgit2/git2go/v31"
corev1 "k8s.io/api/core/v1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
helmv2beta1 "github.com/fluxcd/helm-controller/api/v2beta1"
automationv1beta1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
reflectorv1beta1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
notiv1beta1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
const defaultBranch = "main"
// getKubernetesCredentials returns a path to a kubeconfig file and a kube client instance.
func getKubernetesCredentials(kubeconfig, aksHost, aksCert, aksKey, aksCa string) (string, client.Client, error) {
tmpDir, err := ioutil.TempDir("", "*-azure-e2e")
if err != nil {
return "", nil, err
}
kubeconfigPath := fmt.Sprintf("%s/kubeconfig", tmpDir)
os.WriteFile(kubeconfigPath, []byte(kubeconfig), 0750)
kubeCfg := &rest.Config{
Host: aksHost,
TLSClientConfig: rest.TLSClientConfig{
CertData: []byte(aksCert),
KeyData: []byte(aksKey),
CAData: []byte(aksCa),
},
}
err = sourcev1.AddToScheme(scheme.Scheme)
if err != nil {
return "", nil, err
}
err = kustomizev1.AddToScheme(scheme.Scheme)
if err != nil {
return "", nil, err
}
err = helmv2beta1.AddToScheme(scheme.Scheme)
if err != nil {
return "", nil, err
}
err = reflectorv1beta1.AddToScheme(scheme.Scheme)
if err != nil {
return "", nil, err
}
err = automationv1beta1.AddToScheme(scheme.Scheme)
if err != nil {
return "", nil, err
}
err = notiv1beta1.AddToScheme(scheme.Scheme)
if err != nil {
return "", nil, err
}
kubeClient, err := client.New(kubeCfg, client.Options{Scheme: scheme.Scheme})
if err != nil {
return "", nil, err
}
return kubeconfigPath, kubeClient, nil
}
// installFlux adds the core Flux components to the cluster specified in the kubeconfig file.
func installFlux(ctx context.Context, kubeClient client.Client, kubeconfigPath, repoUrl, azdoPat string, sp spConfig) error {
namespace := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "flux-system",
},
}
_, err := controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &namespace, func() error {
return nil
})
httpsCredentials := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "https-credentials", Namespace: "flux-system"}}
_, err = controllerutil.CreateOrUpdate(ctx, kubeClient, httpsCredentials, func() error {
httpsCredentials.StringData = map[string]string{
"username": "git",
"password": azdoPat,
}
return nil
})
if err != nil {
return err
}
azureSp := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "azure-sp", Namespace: "flux-system"}}
_, err = controllerutil.CreateOrUpdate(ctx, kubeClient, azureSp, func() error {
azureSp.StringData = map[string]string{
"AZURE_TENANT_ID": sp.tenantId,
"AZURE_CLIENT_ID": sp.clientId,
"AZURE_CLIENT_SECRET": sp.clientSecret,
}
return nil
})
if err != nil {
return err
}
// Install Flux and push files to git repository
repo, repoDir, err := getRepository(repoUrl, defaultBranch, true, azdoPat)
if err != nil {
return err
}
err = runCommand(ctx, repoDir, "mkdir -p ./clusters/e2e/flux-system")
if err != nil {
return err
}
err = runCommand(ctx, repoDir, "flux install --components-extra=\"image-reflector-controller,image-automation-controller\" --export > ./clusters/e2e/flux-system/gotk-components.yaml")
if err != nil {
return err
}
err = runCommand(ctx, repoDir, fmt.Sprintf("flux create source git flux-system --git-implementation=libgit2 --url=%s --branch=%s --secret-ref=https-credentials --interval=1m --export > ./clusters/e2e/flux-system/gotk-sync.yaml", repoUrl, defaultBranch))
if err != nil {
return err
}
err = runCommand(ctx, repoDir, "flux create kustomization flux-system --source=flux-system --path='./clusters/e2e' --prune=true --interval=1m --export >> ./clusters/e2e/flux-system/gotk-sync.yaml")
if err != nil {
return err
}
kustomizeYaml := `
resources:
- gotk-components.yaml
- gotk-sync.yaml
patchesStrategicMerge:
- |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: kustomize-controller
namespace: flux-system
spec:
template:
spec:
containers:
- name: manager
envFrom:
- secretRef:
name: azure-sp
- |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: source-controller
namespace: flux-system
spec:
template:
spec:
containers:
- name: manager
envFrom:
- secretRef:
name: azure-sp
`
err = runCommand(ctx, repoDir, fmt.Sprintf("echo \"%s\" > ./clusters/e2e/flux-system/kustomization.yaml", kustomizeYaml))
if err != nil {
return err
}
err = commitAndPushAll(repo, defaultBranch, azdoPat)
if err != nil {
return err
}
// Need to apply CRDs first to make sure that the sync resources will apply properly
err = runCommand(ctx, repoDir, fmt.Sprintf("kubectl --kubeconfig=%s apply -f ./clusters/e2e/flux-system/gotk-components.yaml", kubeconfigPath))
if err != nil {
return err
}
err = runCommand(ctx, repoDir, fmt.Sprintf("kubectl --kubeconfig=%s apply -k ./clusters/e2e/flux-system/", kubeconfigPath))
if err != nil {
return err
}
return nil
}
func runCommand(ctx context.Context, dir, command string) error {
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()
cmd := exec.CommandContext(timeoutCtx, "bash", "-c", command)
cmd.Dir = dir
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failure to run command %s: %v", string(output), err)
}
return nil
}
// verifyGitAndKustomization checks that the gitrespository and kustomization combination are working properly.
func verifyGitAndKustomization(ctx context.Context, kubeClient client.Client, namespace, name string) error {
nn := types.NamespacedName{
Name: name,
Namespace: namespace,
}
source := &sourcev1.GitRepository{}
err := kubeClient.Get(ctx, nn, source)
if err != nil {
return err
}
if apimeta.IsStatusConditionPresentAndEqual(source.Status.Conditions, meta.ReadyCondition, metav1.ConditionTrue) == false {
return fmt.Errorf("source condition not ready")
}
kustomization := &kustomizev1.Kustomization{}
err = kubeClient.Get(ctx, nn, kustomization)
if err != nil {
return err
}
if apimeta.IsStatusConditionPresentAndEqual(kustomization.Status.Conditions, meta.ReadyCondition, metav1.ConditionTrue) == false {
return fmt.Errorf("kustomization condition not ready")
}
return nil
}
func setupNamespace(ctx context.Context, kubeClient client.Client, repoUrl, password, name string) error {
namespace := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
_, err := controllerutil.CreateOrUpdate(ctx, kubeClient, &namespace, func() error {
return nil
})
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "https-credentials",
Namespace: name,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, kubeClient, &secret, func() error {
secret.StringData = map[string]string{
"username": "git",
"password": password,
}
return nil
})
source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}}
_, err = controllerutil.CreateOrUpdate(ctx, kubeClient, source, func() error {
source.Spec = sourcev1.GitRepositorySpec{
Interval: metav1.Duration{
Duration: 1 * time.Minute,
},
GitImplementation: sourcev1.LibGit2Implementation,
Reference: &sourcev1.GitRepositoryRef{
Branch: name,
},
SecretRef: &meta.LocalObjectReference{
Name: "https-credentials",
},
URL: repoUrl,
}
return nil
})
if err != nil {
return err
}
kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}}
_, err = controllerutil.CreateOrUpdate(ctx, kubeClient, kustomization, func() error {
kustomization.Spec = kustomizev1.KustomizationSpec{
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Kind: sourcev1.GitRepositoryKind,
Name: source.Name,
Namespace: source.Namespace,
},
Interval: metav1.Duration{
Duration: 1 * time.Minute,
},
Prune: true,
}
return nil
})
if err != nil {
return err
}
return nil
}
func getRepository(url, branchName string, overrideBranch bool, password string) (*git2go.Repository, string, error) {
checkoutBranch := defaultBranch
if overrideBranch == false {
checkoutBranch = branchName
}
tmpDir, err := ioutil.TempDir("", "*-repository")
if err != nil {
return nil, "", err
}
repo, err := git2go.Clone(url, tmpDir, &git2go.CloneOptions{
FetchOptions: &git2go.FetchOptions{
RemoteCallbacks: git2go.RemoteCallbacks{
CredentialsCallback: credentialCallback("git", password),
},
},
CheckoutBranch: checkoutBranch,
})
if err != nil {
return nil, "", err
}
// Nothing to do further if correct branch is checked out
if checkoutBranch == branchName {
return repo, tmpDir, nil
}
head, err := repo.Head()
if err != nil {
return nil, "", err
}
headCommit, err := repo.LookupCommit(head.Target())
if err != nil {
return nil, "", err
}
_, err = repo.CreateBranch(branchName, headCommit, true)
if err != nil {
return nil, "", err
}
return repo, tmpDir, nil
}
func addFile(dir, path, content string) error {
err := os.WriteFile(filepath.Join(dir, path), []byte(content), 0777)
if err != nil {
return err
}
return nil
}
func commitAndPushAll(repo *git2go.Repository, branchName, password string) error {
idx, err := repo.Index()
if err != nil {
return err
}
err = idx.AddAll([]string{}, git2go.IndexAddDefault, nil)
if err != nil {
return err
}
treeId, err := idx.WriteTree()
if err != nil {
return err
}
err = idx.Write()
if err != nil {
return err
}
tree, err := repo.LookupTree(treeId)
if err != nil {
return err
}
branch, err := repo.LookupBranch(branchName, git2go.BranchLocal)
if err != nil {
return err
}
commitTarget, err := repo.LookupCommit(branch.Target())
if err != nil {
return err
}
sig := &git2go.Signature{
Name: "git",
Email: "test@example.com",
When: time.Now(),
}
_, err = repo.CreateCommit(fmt.Sprintf("refs/heads/%s", branchName), sig, sig, "add file", tree, commitTarget)
if err != nil {
return err
}
origin, err := repo.Remotes.Lookup("origin")
if err != nil {
return err
}
err = origin.Push([]string{fmt.Sprintf("+refs/heads/%s", branchName)}, &git2go.PushOptions{
RemoteCallbacks: git2go.RemoteCallbacks{
CredentialsCallback: credentialCallback("git", password),
},
})
if err != nil {
return err
}
return nil
}
func createTagAndPush(repo *git2go.Repository, branchName, tag, password string) error {
branch, err := repo.LookupBranch(branchName, git2go.BranchAll)
if err != nil {
return err
}
commit, err := repo.LookupCommit(branch.Target())
if err != nil {
return err
}
tags, err := repo.Tags.List()
if err != nil {
return err
}
for _, existingTag := range tags {
if existingTag == tag {
err = repo.Tags.Remove(tag)
if err != nil {
return err
}
}
}
sig := &git2go.Signature{
Name: "git",
Email: "test@example.com",
When: time.Now(),
}
_, err = repo.Tags.Create(tag, commit, sig, "create tag")
if err != nil {
return err
}
origin, err := repo.Remotes.Lookup("origin")
if err != nil {
return err
}
err = origin.Push([]string{fmt.Sprintf("+refs/tags/%s", tag)}, &git2go.PushOptions{
RemoteCallbacks: git2go.RemoteCallbacks{
CredentialsCallback: credentialCallback("git", password),
},
})
if err != nil {
return err
}
return nil
}
func credentialCallback(username, password string) git2go.CredentialsCallback {
return func(url string, usernameFromURL string, allowedTypes git2go.CredType) (*git2go.Cred, error) {
cred, err := git2go.NewCredentialUserpassPlaintext(username, password)
if err != nil {
return nil, err
}
return cred, nil
}
}
func getTestManifest(namespace string) string {
return fmt.Sprintf(`
apiVersion: v1
kind: Namespace
metadata:
name: %s
---
apiVersion: v1
kind: ConfigMap
metadata:
name: foobar
namespace: %s
`, namespace, namespace)
}
Loading…
Cancel
Save