From 58b4c980c12711ad6eed2661ab5f12df8c6fa5dc Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Mon, 4 Jan 2021 15:00:17 +0000 Subject: [PATCH 01/54] Make sure flux create output gets redirected to file (i.e.: a missing `>`) Signed-off-by: Michael Bridgen --- docs/guides/image-update.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/image-update.md b/docs/guides/image-update.md index 128521c8..27d6ee17 100644 --- a/docs/guides/image-update.md +++ b/docs/guides/image-update.md @@ -74,7 +74,7 @@ synchronize with the specified path inside the repository. ## Deploy a demo app -We'll be using a tiny webapp called [podinfo](https://github.com/stefanprodan/podinfo) to +We'll be using a tiny webapp called [podinfo](https://github.com/stefanprodan/podinfo) to showcase the image update feature. Clone your repository with: @@ -235,7 +235,7 @@ flux create image update flux-system \ --author-name=fluxcdbot \ --author-email=fluxcdbot@users.noreply.github.com \ --commit-template="[ci skip] update image" \ ---export ./clusters/my-cluster/flux-system-automation.yaml +--export > ./clusters/my-cluster/flux-system-automation.yaml ``` The above command generates the following manifest: From dd0b807fe432d0fe5f4997aacae499dd916818b3 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Mon, 4 Jan 2021 16:30:44 +0100 Subject: [PATCH 02/54] Validates components set Signed-off-by: Somtochi Onyekwere Signed-off-by: Somtochi Onyekwere --- cmd/flux/bootstrap.go | 5 +++++ cmd/flux/install.go | 4 ++++ internal/utils/utils.go | 13 +++++++++++++ pkg/manifestgen/install/options.go | 2 ++ 4 files changed, 24 insertions(+) diff --git a/cmd/flux/bootstrap.go b/cmd/flux/bootstrap.go index 55b80e93..7f92ca51 100644 --- a/cmd/flux/bootstrap.go +++ b/cmd/flux/bootstrap.go @@ -104,6 +104,11 @@ func bootstrapValidate() error { return fmt.Errorf("component %s is required", component) } } + + if err := utils.ValidateComponents(components); err != nil { + return err + } + return nil } diff --git a/cmd/flux/install.go b/cmd/flux/install.go index ad96d883..2550976c 100644 --- a/cmd/flux/install.go +++ b/cmd/flux/install.go @@ -110,6 +110,10 @@ func installCmdRun(cmd *cobra.Command, args []string) error { components := append(installDefaultComponents, installExtraComponents...) + if err := utils.ValidateComponents(components); err != nil { + return err + } + opts := install.Options{ BaseURL: installManifestsPath, Version: installVersion, diff --git a/internal/utils/utils.go b/internal/utils/utils.go index fd38a9d9..8eb3f239 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -21,6 +21,7 @@ import ( "bytes" "context" "fmt" + "github.com/fluxcd/flux2/pkg/manifestgen/install" "io" "io/ioutil" "os" @@ -380,3 +381,15 @@ func PrintTable(writer io.Writer, header []string, rows [][]string) { table.AppendBulk(rows) table.Render() } + +func ValidateComponents(components []string) error { + defaults := install.MakeDefaultOptions() + bootstrapAllComponents := append(defaults.Components, defaults.ComponentsExtra...) + for _, component := range components { + if !ContainsItemString(bootstrapAllComponents, component) { + return fmt.Errorf("component %s is not available", component) + } + } + + return nil +} diff --git a/pkg/manifestgen/install/options.go b/pkg/manifestgen/install/options.go index 1197b59b..6689a7f7 100644 --- a/pkg/manifestgen/install/options.go +++ b/pkg/manifestgen/install/options.go @@ -23,6 +23,7 @@ type Options struct { Version string Namespace string Components []string + ComponentsExtra []string EventsAddr string Registry string ImagePullSecret string @@ -42,6 +43,7 @@ func MakeDefaultOptions() Options { Version: "latest", Namespace: "flux-system", Components: []string{"source-controller", "kustomize-controller", "helm-controller", "notification-controller"}, + ComponentsExtra: []string{"image-reflector-controller", "image-automation-controller"}, EventsAddr: "", Registry: "ghcr.io/fluxcd", ImagePullSecret: "", From ea337cf83954a309de2a1426dca321f5547bf3ce Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Mon, 14 Dec 2020 11:05:31 +0100 Subject: [PATCH 03/54] Initial docs for core concepts Signed-off-by: Somtochi Onyekwere --- docs/core-concepts/index.md | 52 +++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 53 insertions(+) create mode 100644 docs/core-concepts/index.md diff --git a/docs/core-concepts/index.md b/docs/core-concepts/index.md new file mode 100644 index 00000000..c68de2eb --- /dev/null +++ b/docs/core-concepts/index.md @@ -0,0 +1,52 @@ +# Core Concepts + +!!! note "Work in progress" + This document is a work in progress. + +These are some core concepts in Flux. + +## GitOps + +GitOps is a way of managing your infrastructure and applications so that whole system is described declaratively and version controlled (most likely in a Git repository), and having an automated process that ensures that the deployed environment matches the state specified in a repository. + +For more information, take a look at ["What is GitOps?"](https://www.gitops.tech/#what-is-gitops). + +## Sources + +A *Source* defines the origin of a source and the requirements to obtain +it (e.g. credentials, version selectors). For example, the latest `1.x` tag +available from a Git repository over SSH. + +Sources produce an artifact that is consumed by other Flux elements to perform +actions, like applying the contents of the artifact on the cluster. A source +may be shared by multiple consumers to deduplicate configuration and/or storage. + +The origin of the source is checked for changes on a defined interval, if +there is a newer version available that matches the criteria, a new artifact +is produced. + +All sources are specified as Custom Resources in a Kubernetes cluster, examples +of sources are `GitRepository`, `HelmRepository` and `Bucket` resources. + +For more information, take a look at [the source controller documentation](../components/source/source.md). + +## Reconciliation + +Reconciliation refers to ensuring that a given state(e.g application running in the cluster, infrasatructure) matches a desired state declaratively defined somewhere(e.g a git repository). There are various examples of these in flux e.g: + +- HelmRelease reconciliation: ensures the state of the Helm release matches what is defined in the resource, performs a release if this is not the case (including revision changes of a HelmChart resource). +- Bucket reconciliation: downloads and archives the contents of the declared bucket on a given interval and stores this as an artifact, records the observed revision of the artifact and the artifact itself in the status of resource. +- [Kustomization](#kustomization) reconciliation: ensures the state of the application deployed on a cluster matches resources contained in a git repository. + +## Kustomization + +The kustomization represents an local set of Kubernetes resources that Flux is supposed to reconcile in the cluster. The reconciliation every one minute by default but this can be specified in the kustomization. If you make any changes to the cluster using `kubectl edit` or `kubectl patch`, it will be promptly reverted. You either suspend the reconciliation or push your changes to a Git repository. + +For more information, take a look at [this documentation](../components/kustomize/kustomization.md). + +## Bootstrap + +The process of installing the Flux components in a complete GitOps way is called a bootstrap. The manifests are applied to the cluster, a `GitRepository` and `Kustomization` are created for the Flux components and the manifests are pushed to an existing Git repository(or a new one is created). Flux can manage itself just as it manages other applications. +The bootstrap is done using the Flux CLI `flux bootstrap`. + +For more information, take a look at [the documentation for the bootstrap command](../cmd/flux_bootstrap.md). \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 8fc0566d..c0881230 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,6 +44,7 @@ markdown_extensions: nav: - Introduction: index.md + - Core Concepts: core-concepts/index.md - Get Started: get-started/index.md - Guides: - Installation: guides/installation.md From f1dab2279d1c714b2c1aa388ecde0e8720ddc947 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Tue, 5 Jan 2021 11:36:51 +0100 Subject: [PATCH 04/54] Small nits Signed-off-by: Somtochi Onyekwere --- docs/core-concepts/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core-concepts/index.md b/docs/core-concepts/index.md index c68de2eb..06965871 100644 --- a/docs/core-concepts/index.md +++ b/docs/core-concepts/index.md @@ -46,7 +46,7 @@ For more information, take a look at [this documentation](../components/kustomiz ## Bootstrap -The process of installing the Flux components in a complete GitOps way is called a bootstrap. The manifests are applied to the cluster, a `GitRepository` and `Kustomization` are created for the Flux components and the manifests are pushed to an existing Git repository(or a new one is created). Flux can manage itself just as it manages other applications. -The bootstrap is done using the Flux CLI `flux bootstrap`. +The process of installing the Flux components in a complete GitOps way is called a bootstrap. The manifests are applied to the cluster, a `GitRepository` and `Kustomization` are created for the Flux components, and the manifests are pushed to an existing Git repository(or a new one is created). Flux can manage itself just as it manages other resources. +The bootstrap is done using the `flux` CLI `flux bootstrap`. For more information, take a look at [the documentation for the bootstrap command](../cmd/flux_bootstrap.md). \ No newline at end of file From f1cfae8f26bd7772d62e60194d447c87c2b93ac4 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Tue, 5 Jan 2021 14:37:49 +0100 Subject: [PATCH 05/54] Validates project name for gitlab Signed-off-by: Somtochi Onyekwere --- cmd/flux/bootstrap_gitlab.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go index 022c131f..0ce55135 100644 --- a/cmd/flux/bootstrap_gitlab.go +++ b/cmd/flux/bootstrap_gitlab.go @@ -23,6 +23,7 @@ import ( "net/url" "os" "path" + "regexp" "time" "github.com/spf13/cobra" @@ -67,6 +68,10 @@ the bootstrap command will perform an upgrade if needed.`, RunE: bootstrapGitLabCmdRun, } +const ( + gitlabProjectRegex = `\A[[:alnum:]\x{00A9}-\x{1f9ff}_][[:alnum:]\p{Pd}\x{00A9}-\x{1f9ff}_\.]*\z` +) + var ( glOwner string glRepository string @@ -97,6 +102,14 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("%s environment variable not found", git.GitLabTokenName) } + projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, glRepository) + if err != nil { + return err + } + if !projectNameIsValid { + return fmt.Errorf("%s is an invalid project name for gitlab.\nIt can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.", glRepository) + } + if err := bootstrapValidate(); err != nil { return err } From a9197030112c5027013eab103a04f97ea7bdfa90 Mon Sep 17 00:00:00 2001 From: Stacey Potter <50154848+staceypotter@users.noreply.github.com> Date: Thu, 31 Dec 2020 12:26:53 -0500 Subject: [PATCH 06/54] Update Upcoming Events & Featured Talks sections Sorted 'featured talks' chronologically, moved Dec 14 talk from upcoming to featured section with video link, added Scott's upcoming Helm talk in Jan 2021. Added 2-Nov-2020 video that was missing Signed-off-by: Stacey Potter <50154848+staceypotter@users.noreply.github.com> --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 22b94cb1..8e8294e4 100644 --- a/README.md +++ b/README.md @@ -108,17 +108,17 @@ Depending on what you want to do, some of the following bits might be your first - Check out [how to contribute](CONTRIBUTING.md) to the project ### Upcoming Events - -- 14 Dec 2020 - [The Power of GitOps with Flux and Flagger with Leigh Capili](https://www.meetup.com/GitOps-Community/events/274924513/) +- 11 Jan 2021 - [Helm + GitOps = ⚡️⚡️⚡️ with Scott Rigby](https://www.meetup.com/GitOps-Community/events/275348736/) ### Featured Talks - +- 14 Dec 2020 - [The Power of GitOps with Flux and Flagger (GitOps Hands-On) with Leigh Capili](https://youtu.be/cB7iXeNLteE) +- 30 Nov 2020 - [The Power of GitOps with Flux 2 - Part 3 with Leigh Capili](https://youtu.be/N_K5g7o9JKg) - 24 Nov 2020 - [Flux CD v2 with GitOps Toolkit - Kubernetes Deployment and Sync Mechanism](https://youtu.be/R6OeIgb7lUI) +- 02 Nov 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 2 with Leigh Capili](https://youtu.be/fC2YCxQRUwU) - 28 Oct 2020 - [The Kubelist Podcast: Flux with Michael Bridgen](https://www.heavybit.com/library/podcasts/the-kubelist-podcast/ep-5-flux-with-michael-bridgen-of-weaveworks/) - 19 Oct 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 1 with Leigh Capili](https://youtu.be/0v5bjysXTL8) -- 30 Nov 2020 - [The Power of GitOps with Flux 2 - Part 3 with Leigh Capili](https://youtu.be/N_K5g7o9JKg) - 12 Oct 2020 - [Rawkode Live: Introduction to GitOps Toolkit with Stefan Prodan](https://youtu.be/HqTzuOBP0eY) -- 4 Sep 2020 - [KubeCon Europe: The road to Flux v2 and Progressive Delivery with Stefan Prodan & Hidde Beydals](https://youtu.be/8v94nUkXsxU) -- 25 June 2020 - [Cloud Native Nordics: Introduction to GitOps & GitOps Toolkit with Alexis Richardson & Stefan Prodan](https://youtu.be/qQBtSkgl7tI) +- 04 Sep 2020 - [KubeCon Europe: The road to Flux v2 and Progressive Delivery with Stefan Prodan & Hidde Beydals](https://youtu.be/8v94nUkXsxU) +- 25 Jun 2020 - [Cloud Native Nordics: Introduction to GitOps & GitOps Toolkit with Alexis Richardson & Stefan Prodan](https://youtu.be/qQBtSkgl7tI) -We are looking forward to seeing you with us! +We look forward to seeing you with us! From 0c0d353e9c40aaca8e51a6eb2c0ce441b39a0aee Mon Sep 17 00:00:00 2001 From: Stacey Potter <50154848+staceypotter@users.noreply.github.com> Date: Tue, 5 Jan 2021 13:21:20 -0500 Subject: [PATCH 07/54] Update upcoming events & featured talks section Put things in chronological order, added new events, moved old ones, switched order of the 2 sections to match fluxcd/flux2 Signed-off-by: Stacey Potter <50154848+staceypotter@users.noreply.github.com> --- docs/index.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/index.md b/docs/index.md index 88a77319..544d8dfd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -84,20 +84,19 @@ Depending on what you want to do, some of the following bits might be your first - And if you are completely new to Flux v2 and the GitOps Toolkit, take a look at our [Get Started guide](get-started/index.md) and give us feedback - Check out [how to contribute](contributing/index.md) to the project -### Featured Talks +### Upcoming Events +- 11 Jan 2021 - [Helm + GitOps = ⚡️⚡️⚡️ with Scott Rigby](https://www.meetup.com/GitOps-Community/events/275348736/) +### Featured Talks +- 14 Dec 2020 - [The Power of GitOps with Flux and Flagger (GitOps Hands-On) with Leigh Capili](https://youtu.be/cB7iXeNLteE) +- 30 Nov 2020 - [The Power of GItOps with Flux 2 - Part 3 with Leigh Capili](https://youtu.be/N_K5g7o9JKg) - 24 Nov 2020 - [Flux CD v2 with GitOps Toolkit - Kubernetes Deployment and Sync Mechanism](https://youtu.be/R6OeIgb7lUI) -- 19 Oct 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 2 with Leigh Capili](https://youtu.be/fC2YCxQRUwU) +- 02 Nov 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 2 with Leigh Capili](https://youtu.be/fC2YCxQRUwU) - 28 Oct 2020 - [The Kubelist Podcast: Flux with Michael Bridgen](https://www.heavybit.com/library/podcasts/the-kubelist-podcast/ep-5-flux-with-michael-bridgen-of-weaveworks/) - 19 Oct 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 1 with Leigh Capili](https://youtu.be/0v5bjysXTL8) - 12 Oct 2020 - [Rawkode Live: Introduction to GitOps Toolkit with Stefan Prodan](https://youtu.be/HqTzuOBP0eY) -- 4 Sep 2020 - [KubeCon Europe: The road to Flux v2 and Progressive Delivery with Stefan Prodan & Hidde Beydals](https://youtu.be/8v94nUkXsxU) +- 04 Sep 2020 - [KubeCon Europe: The road to Flux v2 and Progressive Delivery with Stefan Prodan & Hidde Beydals](https://youtu.be/8v94nUkXsxU) - 25 June 2020 - [Cloud Native Nordics: Introduction to GitOps & GitOps Toolkit with Alexis Richardson & Stefan Prodan](https://youtu.be/qQBtSkgl7tI) -- 7 May 2020 - [GitOps Days - Community Special: GitOps Toolkit Experimentation with Stefan Prodan](https://youtu.be/WHzxunv4DKk?t=6521) - -### Upcoming Events - -- 12-13 Nov 2020 - [GitOps Days EMEA](https://www.gitopsdays.com/) with talks and workshops on migrating to Flux v2 and Helm Controller -- 19 Nov 2020 - [KubeCon NA: Progressive Delivery Techniques with Flagger and Flux v2 with Stefan Prodan](https://kccncna20.sched.com/event/1b04f8408b49976b843a5d0019cb8112) +- 07 May 2020 - [GitOps Days - Community Special: GitOps Toolkit Experimentation with Stefan Prodan](https://youtu.be/WHzxunv4DKk?t=6521) -We are looking forward to seeing you with us! +We look forward to seeing you with us! From c034befbb509be0c06ecb046b64902d83b160d29 Mon Sep 17 00:00:00 2001 From: fluxcdbot Date: Wed, 6 Jan 2021 09:59:33 +0000 Subject: [PATCH 08/54] Update toolkit components --- go.mod | 2 +- go.sum | 4 ++-- .../bases/image-automation-controller/kustomization.yaml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 95c9193b..2054cbd7 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/cyphar/filepath-securejoin v0.2.2 github.com/fluxcd/helm-controller/api v0.4.4 - github.com/fluxcd/image-automation-controller/api v0.1.0 + github.com/fluxcd/image-automation-controller/api v0.2.0 github.com/fluxcd/image-reflector-controller/api v0.1.0 github.com/fluxcd/kustomize-controller/api v0.5.3 github.com/fluxcd/notification-controller/api v0.5.0 diff --git a/go.sum b/go.sum index 5d698e40..1071becb 100644 --- a/go.sum +++ b/go.sum @@ -177,8 +177,8 @@ github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fluxcd/helm-controller/api v0.4.4 h1:WYf7KokS3ALeE1F2SrviMHtEBGfznP7DkxXwo5pP7f8= github.com/fluxcd/helm-controller/api v0.4.4/go.mod h1:H3fHkKJWcxPz38L1kxBX/MGm5v9XKzeoKZWNM7+dW2o= -github.com/fluxcd/image-automation-controller/api v0.1.0 h1:XN/BbhCRoISEb828rfMt2fNe+3s4Zwc+BwhRi3K1SHA= -github.com/fluxcd/image-automation-controller/api v0.1.0/go.mod h1:DHjFNvA+kJlSm7cbTaG+Z5smVjMjLw7xzlJc9brP0zY= +github.com/fluxcd/image-automation-controller/api v0.2.0 h1:1diY89HhpbseqENkd9DLgqsDnkTGZmVDjX5mucNOMr8= +github.com/fluxcd/image-automation-controller/api v0.2.0/go.mod h1:DHjFNvA+kJlSm7cbTaG+Z5smVjMjLw7xzlJc9brP0zY= github.com/fluxcd/image-reflector-controller/api v0.1.0 h1:wlqwCy4sZMbbdrgSY9Fd0mfy55kk7dS4Z+icrDlkmmg= github.com/fluxcd/image-reflector-controller/api v0.1.0/go.mod h1:u7vnULekPHXAZgJ35lqCjV2MaJVN0xbD+qt9X9TVCMs= github.com/fluxcd/kustomize-controller/api v0.5.3 h1:zZiWFBQkNytLffOOJJttGFQk7BIUHpT2yoOCG4nyqII= diff --git a/manifests/bases/image-automation-controller/kustomization.yaml b/manifests/bases/image-automation-controller/kustomization.yaml index 2edf16cb..a535827c 100644 --- a/manifests/bases/image-automation-controller/kustomization.yaml +++ b/manifests/bases/image-automation-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/image-automation-controller/archive/v0.1.0.zip//image-automation-controller-0.1.0/config/crd -- https://github.com/fluxcd/image-automation-controller/archive/v0.1.0.zip//image-automation-controller-0.1.0/config/manager +- https://github.com/fluxcd/image-automation-controller/archive/v0.2.0.zip//image-automation-controller-0.2.0/config/crd +- https://github.com/fluxcd/image-automation-controller/archive/v0.2.0.zip//image-automation-controller-0.2.0/config/manager patchesJson6902: - target: group: apps From c5465de000da39bcf1669da998896923680fcd88 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 6 Jan 2021 12:29:41 +0100 Subject: [PATCH 09/54] Update helm release guide with how to use cloud storage backends Signed-off-by: Philip Laine --- docs/guides/helmreleases.md | 97 ++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 35 deletions(-) diff --git a/docs/guides/helmreleases.md b/docs/guides/helmreleases.md index df00896d..723daeae 100644 --- a/docs/guides/helmreleases.md +++ b/docs/guides/helmreleases.md @@ -117,52 +117,79 @@ repository and omits all other files. HTTP/S basic and SSH authentication can be configured for private Git repositories. See the [`GitRepository` CRD docs](../components/source/gitrepositories.md) for more details. - -### Bucket -Charts from S3 compatible storage buckets can be released by declaring -a `Bucket`, the source-controller will fetch the contents of the bucket -on an interval and expose it as an artifact. +### Cloud Storage -**There is one caveat you should be aware of:** to make the -source-controller produce a new chart artifact, the `version` in the -`Chart.yaml` of the chart must be bumped. +It is inadvisable while still possible to use a `Bucket` as a source for a `HelmRelease`, +as the whole storage bucket will be downloaded by source controller at each sync. The +bucket can easily become very large if there are frequent releases of multiple charts +that are stored in the same bucket. + +A better option is to use [Chartmuseum](https://github.com/helm/chartmuseum) and run a cluster +local Helm repository that can be used by source controller. Chartmuseum has support +for multiple different cloud storage solutions such as S3, GCS, and Azure Blob Storage, +meaning that you are not limited to only using storage providers that support the S3 protocol. -An example `Bucket`: +You can deploy a Chartmuseum instance with a `HelmRelease` that exposes a Helm repository stored +in a S3 bucket. Please refer to [Chartmuseums how to run documentation](https://chartmuseum.com/docs/#how-to-run) +for details about how to use other storage backends. ```yaml apiVersion: source.toolkit.fluxcd.io/v1beta1 -kind: Bucket +kind: HelmRepository metadata: - name: podinfo - namespace: gotk-system + name: chartmuseum + namespace: flux-system spec: - interval: 1m - provider: generic - bucketName: podinfo - endpoint: minio.minio.svc.cluster.local:9000 - ignore: | - # exclude all - /* - # include charts directory - !/charts/ + url: https://chartmuseum.github.io/charts + interval: 10m +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: chartmuseum + namespace: flux-system +spec: + interval: 5m + chart: + spec: + chart: chartmuseum + version: "2.14.2" + sourceRef: + kind: HelmRepository + name: chartmuseum + namespace: flux-system + interval: 1m + values: + env: + open: + AWS_SDK_LOAD_CONFIG: true + STORAGE: amazon + STORAGE_AMAZON_BUCKET: "bucket-name" + STORAGE_AMAZON_PREFIX: "" + STORAGE_AMAZON_REGION: "region-name" + serviceAccount: + create: true + annotations: + eks.amazonaws.com/role-arn: "role-arn" + securityContext: + enabled: true + fsGroup: 65534 ``` -The `interval` defines at which interval the Git repository contents -are fetched, and should be at least `1m`. Setting this to a higher -value means newer chart versions will be detected at a slower pace, -a push-based fetch can be introduced using [webhook receivers](webhook-receivers.md) - -The `provider`, `bucketName` and `endpoint` together define what -S3 compatible storage should be connected to. For more information, -see the [`Bucket` CRD docs](../components/source/buckets.md). +After Chartmuseum is up and running it should be possible to use the accompanying +service as the url for the `HelmRepository`. - -The `ignore` defines file and folder exclusion for the -artifact produced, and follows the [`.gitignore` pattern -format](https://git-scm.com/docs/gitignore#_pattern_format). -The above example only includes the `charts` directory of the -repository and omits all other files. +```yaml +apiVersion: source.toolkit.fluxcd.io/v1beta1 +kind: HelmRepository +metadata: + name: helm-charts + namespace: flux-system +spec: + interval: 1m + url: http://chartmuseum-chartmuseum:8080 +``` ## Define a Helm release From 7dcfbdbb2942b42cc79ad11cdae85b8932f2890e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Javier=20Se=C3=B1aris?= Date: Wed, 6 Jan 2021 10:38:54 -0500 Subject: [PATCH 10/54] Minor fixes Fix small typo. Added verb to make the intent clear Added spaces before "(" to match the same pattern used in other parts of the docs Signed-off-by: Jose Javier Senaris Carballo --- docs/core-concepts/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/core-concepts/index.md b/docs/core-concepts/index.md index 06965871..1569924a 100644 --- a/docs/core-concepts/index.md +++ b/docs/core-concepts/index.md @@ -32,7 +32,7 @@ For more information, take a look at [the source controller documentation](../co ## Reconciliation -Reconciliation refers to ensuring that a given state(e.g application running in the cluster, infrasatructure) matches a desired state declaratively defined somewhere(e.g a git repository). There are various examples of these in flux e.g: +Reconciliation refers to ensuring that a given state (e.g application running in the cluster, infrastructure) matches a desired state declaratively defined somewhere (e.g a git repository). There are various examples of these in flux e.g: - HelmRelease reconciliation: ensures the state of the Helm release matches what is defined in the resource, performs a release if this is not the case (including revision changes of a HelmChart resource). - Bucket reconciliation: downloads and archives the contents of the declared bucket on a given interval and stores this as an artifact, records the observed revision of the artifact and the artifact itself in the status of resource. @@ -40,13 +40,13 @@ Reconciliation refers to ensuring that a given state(e.g application running in ## Kustomization -The kustomization represents an local set of Kubernetes resources that Flux is supposed to reconcile in the cluster. The reconciliation every one minute by default but this can be specified in the kustomization. If you make any changes to the cluster using `kubectl edit` or `kubectl patch`, it will be promptly reverted. You either suspend the reconciliation or push your changes to a Git repository. +The kustomization represents a local set of Kubernetes resources that Flux is supposed to reconcile in the cluster. The reconciliation runs every one minute by default but this can be specified in the kustomization. If you make any changes to the cluster using `kubectl edit` or `kubectl patch`, it will be promptly reverted. You either suspend the reconciliation or push your changes to a Git repository. For more information, take a look at [this documentation](../components/kustomize/kustomization.md). ## Bootstrap -The process of installing the Flux components in a complete GitOps way is called a bootstrap. The manifests are applied to the cluster, a `GitRepository` and `Kustomization` are created for the Flux components, and the manifests are pushed to an existing Git repository(or a new one is created). Flux can manage itself just as it manages other resources. +The process of installing the Flux components in a complete GitOps way is called a bootstrap. The manifests are applied to the cluster, a `GitRepository` and `Kustomization` are created for the Flux components, and the manifests are pushed to an existing Git repository (or a new one is created). Flux can manage itself just as it manages other resources. The bootstrap is done using the `flux` CLI `flux bootstrap`. -For more information, take a look at [the documentation for the bootstrap command](../cmd/flux_bootstrap.md). \ No newline at end of file +For more information, take a look at [the documentation for the bootstrap command](../cmd/flux_bootstrap.md). From ad90d37f146fe95a6a5373a95569a342a48b6c91 Mon Sep 17 00:00:00 2001 From: Aurel Canciu Date: Wed, 6 Jan 2021 17:43:29 +0200 Subject: [PATCH 11/54] Add documentation for ECR authentication Document a workaround solution for users to rely on until native image repository authentication is implemented for supported cloud providers. Signed-off-by: Aurel Canciu --- docs/guides/image-update.md | 142 ++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/docs/guides/image-update.md b/docs/guides/image-update.md index 27d6ee17..54540652 100644 --- a/docs/guides/image-update.md +++ b/docs/guides/image-update.md @@ -358,3 +358,145 @@ images: newName: ghcr.io/stefanprodan/podinfo newTag: 5.0.0 # {"$imagepolicy": "flux-system:podinfo:tag"} ``` + +## ImageRepository cloud providers authentication + +If relying on a cloud provider image repository, you might need to do some extra +work in order to configure the ImageRepository resource credentials. Here are +some common examples for the most popular cloud provider docker registries. + +!!! warning "Workarounds" + The examples below are intended as workaround solutions until native + authentication mechanisms are implemented in Flux itself to support this in + a more straightforward manner. + +### AWS Elastic Container Registry + +The registry authentication credentials for ECR expire every 12 hours. +Considering this limitation, one needs to ensure the credentials are being +refreshed before expiration so that the controller can rely on them for +authentication. + +The solution proposed is to create a cronjob that runs every 6 hours which would +re-create the `docker-registry` secret using a new token. + +Edit and save the following snippet to a file +`./clusters/my-cluster/ecr-sync.yaml`, commit and push it to git. + +```yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: ecr-credentials-sync + namespace: flux-system +rules: +- apiGroups: [""] + resources: + - secrets + verbs: + - delete + - create +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: ecr-credentials-sync + namespace: flux-system +subjects: +- kind: ServiceAccount + name: ecr-credentials-sync +roleRef: + kind: Role + name: ecr-credentials-sync + apiGroup: "" +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ecr-credentials-sync + # Uncomment and edit if using IRSA + # annotations: + # eks.amazonaws.com/role-arn: +--- +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: ecr-credentials-sync + namespace: flux-system +spec: + suspend: false + schedule: 0 */6 * * * + failedJobsHistoryLimit: 1 + successfulJobsHistoryLimit: 1 + jobTemplate: + spec: + template: + spec: + serviceAccountName: ecr-credentials-sync + restartPolicy: Never + volumes: + - name: token + emptyDir: + medium: Memory + initContainers: + - image: amazon/aws-cli + name: get-token + imagePullPolicy: IfNotPresent + # You will need to set the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables if not using + # IRSA. It is recommended to store the values in a Secret and load them in the container using envFrom. + # envFrom: + # - secretRef: + # name: aws-credentials + env: + - name: REGION + value: us-east-1 # change this if ECR repo is in a different region + volumeMounts: + - mountPath: /token + name: token + command: + - /bin/sh + - -ce + - aws ecr get-login-password --region ${REGION} > /token/ecr-token + containers: + - image: bitnami/kubectl + name: create-secret + imagePullPolicy: IfNotPresent + env: + - name: SECRET_NAME + value: # this is the generated Secret name + - name: + value: .dkr.ecr..amazonaws.com # fill in the account id and region + volumeMounts: + - mountPath: /token + name: token + command: + - /bin/bash + - -ce + - |- + kubectl delete secret --ignore-not-found $SECRET_NAME + kubectl create secret docker-registry $SECRET_NAME \ + --docker-server="$ECR_REGISTRY" \ + --docker-username=AWS \ + --docker-password="$( Date: Thu, 7 Jan 2021 09:29:37 +0100 Subject: [PATCH 12/54] Coverts backward slash to forward slash in path flag Signed-off-by: Somtochi Onyekwere --- cmd/flux/bootstrap_github.go | 3 ++- cmd/flux/bootstrap_gitlab.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/flux/bootstrap_github.go b/cmd/flux/bootstrap_github.go index f85e1ec1..b9cf8450 100644 --- a/cmd/flux/bootstrap_github.go +++ b/cmd/flux/bootstrap_github.go @@ -23,6 +23,7 @@ import ( "net/url" "os" "path" + "path/filepath" "time" "github.com/spf13/cobra" @@ -261,7 +262,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { // configure repo synchronization logger.Actionf("generating sync manifests") - syncManifests, err := generateSyncManifests(repoURL, bootstrapBranch, namespace, namespace, ghPath.String(), tmpDir, ghInterval) + syncManifests, err := generateSyncManifests(repoURL, bootstrapBranch, namespace, namespace, filepath.ToSlash(ghPath.String()), tmpDir, ghInterval) if err != nil { return err } diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go index 0ce55135..e8b9556c 100644 --- a/cmd/flux/bootstrap_gitlab.go +++ b/cmd/flux/bootstrap_gitlab.go @@ -23,6 +23,7 @@ import ( "net/url" "os" "path" + "path/filepath" "regexp" "time" @@ -240,7 +241,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { // configure repo synchronization logger.Actionf("generating sync manifests") - syncManifests, err := generateSyncManifests(repoURL, bootstrapBranch, namespace, namespace, glPath.String(), tmpDir, glInterval) + syncManifests, err := generateSyncManifests(repoURL, bootstrapBranch, namespace, namespace, filepath.ToSlash(glPath.String()), tmpDir, glInterval) if err != nil { return err } From 0646538ceff26fc913a9ab00fdc15bffdb35f779 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Thu, 7 Jan 2021 10:13:59 +0100 Subject: [PATCH 13/54] Checks if bootstrap path differs Signed-off-by: Somtochi Onyekwere --- cmd/flux/bootstrap.go | 17 +++++++++++++++++ cmd/flux/bootstrap_github.go | 22 ++++++++++++++-------- cmd/flux/bootstrap_gitlab.go | 22 ++++++++++++++-------- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/cmd/flux/bootstrap.go b/cmd/flux/bootstrap.go index 7f92ca51..fd53ddc0 100644 --- a/cmd/flux/bootstrap.go +++ b/cmd/flux/bootstrap.go @@ -266,3 +266,20 @@ func generateDeployKey(ctx context.Context, kubeClient client.Client, url *url.U return string(pair.PublicKey), nil } + +func checkIfBootstrapPathDiffers(ctx context.Context, kubeClient client.Client, namespace string, path string) bool { + namespacedName := types.NamespacedName{ + Name: namespace, + Namespace: namespace, + } + var fluxSystemKustomization kustomizev1.Kustomization + err := kubeClient.Get(ctx, namespacedName, &fluxSystemKustomization) + if err != nil { + return false + } + if fluxSystemKustomization.Spec.Path == path { + return false + } + + return true +} diff --git a/cmd/flux/bootstrap_github.go b/cmd/flux/bootstrap_github.go index b9cf8450..2a12991d 100644 --- a/cmd/flux/bootstrap_github.go +++ b/cmd/flux/bootstrap_github.go @@ -115,6 +115,20 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { return err } + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) + if err != nil { + return err + } + + bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, namespace, filepath.ToSlash(ghPath.String())) + + if bootstrapPathDiffers { + return fmt.Errorf("cluster already bootstrapped to a different path") + } + repository, err := git.NewRepository(ghRepository, ghOwner, ghHostname, ghToken, "flux", ghOwner+"@users.noreply.github.com") if err != nil { return err @@ -135,9 +149,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { } defer os.RemoveAll(tmpDir) - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - if ghDelete { if err := provider.DeleteRepository(ctx, repository); err != nil { return err @@ -198,11 +209,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { logger.Successf("components are up to date") } - kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) - if err != nil { - return err - } - // determine if repo synchronization is working isInstall := shouldInstallManifests(ctx, kubeClient, namespace) diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go index e8b9556c..63677466 100644 --- a/cmd/flux/bootstrap_gitlab.go +++ b/cmd/flux/bootstrap_gitlab.go @@ -115,6 +115,20 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { return err } + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) + if err != nil { + return err + } + + bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, namespace, filepath.ToSlash(glPath.String())) + + if bootstrapPathDiffers { + return fmt.Errorf("cluster already bootstrapped to a different path") + } + repository, err := git.NewRepository(glRepository, glOwner, glHostname, glToken, "flux", glOwner+"@users.noreply.gitlab.com") if err != nil { return err @@ -129,20 +143,12 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { IsPersonal: glPersonal, } - kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) - if err != nil { - return err - } - tmpDir, err := ioutil.TempDir("", namespace) if err != nil { return err } defer os.RemoveAll(tmpDir) - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - // create GitLab project if doesn't exists logger.Actionf("connecting to %s", glHostname) changed, err := provider.CreateRepository(ctx, repository) From b8d4af5538447e42b116f6c104c449985acc88b7 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Thu, 7 Jan 2021 10:30:17 +0100 Subject: [PATCH 14/54] Inform user of path being used Signed-off-by: Somtochi Onyekwere --- cmd/flux/bootstrap.go | 8 ++++---- cmd/flux/bootstrap_github.go | 4 ++-- cmd/flux/bootstrap_gitlab.go | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/flux/bootstrap.go b/cmd/flux/bootstrap.go index fd53ddc0..ddfc2ff2 100644 --- a/cmd/flux/bootstrap.go +++ b/cmd/flux/bootstrap.go @@ -267,7 +267,7 @@ func generateDeployKey(ctx context.Context, kubeClient client.Client, url *url.U return string(pair.PublicKey), nil } -func checkIfBootstrapPathDiffers(ctx context.Context, kubeClient client.Client, namespace string, path string) bool { +func checkIfBootstrapPathDiffers(ctx context.Context, kubeClient client.Client, namespace string, path string) (string, bool) { namespacedName := types.NamespacedName{ Name: namespace, Namespace: namespace, @@ -275,11 +275,11 @@ func checkIfBootstrapPathDiffers(ctx context.Context, kubeClient client.Client, var fluxSystemKustomization kustomizev1.Kustomization err := kubeClient.Get(ctx, namespacedName, &fluxSystemKustomization) if err != nil { - return false + return "", false } if fluxSystemKustomization.Spec.Path == path { - return false + return "", false } - return true + return fluxSystemKustomization.Spec.Path, true } diff --git a/cmd/flux/bootstrap_github.go b/cmd/flux/bootstrap_github.go index 2a12991d..5c4158c2 100644 --- a/cmd/flux/bootstrap_github.go +++ b/cmd/flux/bootstrap_github.go @@ -123,10 +123,10 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { return err } - bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, namespace, filepath.ToSlash(ghPath.String())) + usedPath, bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, namespace, filepath.ToSlash(ghPath.String())) if bootstrapPathDiffers { - return fmt.Errorf("cluster already bootstrapped to a different path") + return fmt.Errorf("cluster already bootstrapped to a %v path", usedPath) } repository, err := git.NewRepository(ghRepository, ghOwner, ghHostname, ghToken, "flux", ghOwner+"@users.noreply.github.com") diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go index 63677466..9bffa71a 100644 --- a/cmd/flux/bootstrap_gitlab.go +++ b/cmd/flux/bootstrap_gitlab.go @@ -123,10 +123,10 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { return err } - bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, namespace, filepath.ToSlash(glPath.String())) + usedPath, bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, namespace, filepath.ToSlash(glPath.String())) if bootstrapPathDiffers { - return fmt.Errorf("cluster already bootstrapped to a different path") + return fmt.Errorf("cluster already bootstrapped to a %v path", usedPath) } repository, err := git.NewRepository(glRepository, glOwner, glHostname, glToken, "flux", glOwner+"@users.noreply.gitlab.com") From 3a4a2002d428d92b95336cf672709ae7be3a1c4f Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Thu, 7 Jan 2021 10:44:40 +0100 Subject: [PATCH 15/54] Corrects typo Signed-off-by: Somtochi Onyekwere --- cmd/flux/bootstrap_github.go | 2 +- cmd/flux/bootstrap_gitlab.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/flux/bootstrap_github.go b/cmd/flux/bootstrap_github.go index 5c4158c2..a0e808d9 100644 --- a/cmd/flux/bootstrap_github.go +++ b/cmd/flux/bootstrap_github.go @@ -126,7 +126,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { usedPath, bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, namespace, filepath.ToSlash(ghPath.String())) if bootstrapPathDiffers { - return fmt.Errorf("cluster already bootstrapped to a %v path", usedPath) + return fmt.Errorf("cluster already bootstrapped to %v path", usedPath) } repository, err := git.NewRepository(ghRepository, ghOwner, ghHostname, ghToken, "flux", ghOwner+"@users.noreply.github.com") diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go index 9bffa71a..1d387ba6 100644 --- a/cmd/flux/bootstrap_gitlab.go +++ b/cmd/flux/bootstrap_gitlab.go @@ -126,7 +126,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { usedPath, bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, namespace, filepath.ToSlash(glPath.String())) if bootstrapPathDiffers { - return fmt.Errorf("cluster already bootstrapped to a %v path", usedPath) + return fmt.Errorf("cluster already bootstrapped to %v path", usedPath) } repository, err := git.NewRepository(glRepository, glOwner, glHostname, glToken, "flux", glOwner+"@users.noreply.gitlab.com") From 57bdaf939a98440c42539ed812bbb6ec7d7a6b96 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Thu, 7 Jan 2021 11:45:46 +0000 Subject: [PATCH 16/54] Cross off tasks in the image automation roadmap Specifically: - integration into `flux` CLI - guide to how to use the controllers Signed-off-by: Michael Bridgen --- docs/roadmap/index.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/roadmap/index.md b/docs/roadmap/index.md index fd1408fd..24aff177 100644 --- a/docs/roadmap/index.md +++ b/docs/roadmap/index.md @@ -48,11 +48,11 @@ Tasks ### Flux image update feature parity -[= 70% "70%"] +[= 80% "80%"] -Image automation is available as a prerelease. See [the -README](https://github.com/fluxcd/image-automation-controller#readme) -for instructions on installing it. +Image automation is available as a prerelease. See [this +guide](https://toolkit.fluxcd.io/guides/image-update/) for how to +install and use it. Goals @@ -73,7 +73,8 @@ Tasks - [ ] Azure-specific support [fluxcd/image-reflector-controller#11](https://github.com/fluxcd/image-reflector-controller/issues/11) - [x] Design the automation component - [x] Implement the image scan/patch/push workflow -- [ ] Integrate the new components in the Flux CLI [fluxcd/flux2#538](https://github.com/fluxcd/flux2/pull/538) +- [x] Integrate the new components in the Flux CLI [fluxcd/flux2#538](https://github.com/fluxcd/flux2/pull/538) +- [x] Write a guide for how to use image automation ([guide here](https://toolkit.fluxcd.io/guides/image-update/)) - [ ] Write a migration guide from Flux annotations ## The road to Helm Operator v2 From 102552427f3b06ab1ae37353b8aea2e325cc4f8c Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Thu, 7 Jan 2021 14:19:19 +0200 Subject: [PATCH 17/54] Update dev guide to point to fluxcd/source-watcher Signed-off-by: Stefan Prodan --- docs/dev-guides/source-watcher.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/dev-guides/source-watcher.md b/docs/dev-guides/source-watcher.md index 00561ff1..46d05061 100644 --- a/docs/dev-guides/source-watcher.md +++ b/docs/dev-guides/source-watcher.md @@ -17,7 +17,7 @@ On your dev machine install the following tools: * kustomize >= 3.5 * docker >= 19.03 -## Install the GitOps Toolkit +## Install Flux Create a cluster for testing: @@ -25,7 +25,7 @@ Create a cluster for testing: kind create cluster --name dev ``` -Install the toolkit CLI: +Install the Flux CLI: ```sh curl -s https://toolkit.fluxcd.io/install.sh | sudo bash @@ -37,7 +37,7 @@ Verify that your dev machine satisfies the prerequisites with: flux check --pre ``` -Install the toolkit controllers on the dev cluster: +Install source-controller on the dev cluster: ```sh flux install @@ -45,13 +45,13 @@ flux install ## Clone the sample controller -You'll be using [stefanprodan/source-watcher](https://github.com/stefanprodan/source-watcher) as +You'll be using [fluxcd/source-watcher](https://github.com/fluxcd/source-watcher) as a template for developing your own controller. The source-watcher was scaffolded with `kubebuilder init`. -Clone the source-watcher repo: +Clone the source-watcher repository: ```sh -git clone https://github.com/stefanprodan/source-watcher +git clone https://github.com/fluxcd/source-watcher cd source-watcher ``` @@ -115,7 +115,7 @@ The source-controller reports the revision under `GitRepository.Status.Artifact. ## How it works -The [GitRepositoryWatcher](https://github.com/stefanprodan/source-watcher/blob/master/controllers/gitrepository_watcher.go) +The [GitRepositoryWatcher](https://github.com/fluxcd/source-watcher/blob/main/controllers/gitrepository_watcher.go) controller does the following: * subscribes to `GitRepository` events @@ -186,8 +186,8 @@ func (r *GitRepositoryWatcher) SetupWithManager(mgr ctrl.Manager) error { To add the watcher to an existing project, copy the controller and the revision change predicate to your `controllers` dir: -* [gitrepository_watcher.go](https://github.com/stefanprodan/source-watcher/blob/master/controllers/gitrepository_watcher.go) -* [gitrepository_predicate.go](https://github.com/stefanprodan/source-watcher/blob/master/controllers/gitrepository_predicate.go) +* [gitrepository_watcher.go](https://github.com/fluxcd/source-watcher/blob/main/controllers/gitrepository_watcher.go) +* [gitrepository_predicate.go](https://github.com/fluxcd/source-watcher/blob/main/controllers/gitrepository_predicate.go) In your `main.go` init function, register the Source API schema: @@ -224,9 +224,9 @@ Your `go.mod` should require controller-runtime v0.6 or newer: ```go require ( - k8s.io/apimachinery v0.18.4 - k8s.io/client-go v0.18.4 - sigs.k8s.io/controller-runtime v0.6.0 + k8s.io/apimachinery v0.19.4 + k8s.io/client-go v0.19.4 + sigs.k8s.io/controller-runtime v0.6.4 ) ``` From febedaad8fa034fbc5e3e2a34182722268c8a105 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 8 Jan 2021 10:36:48 +0200 Subject: [PATCH 18/54] Map ecdsa/ed25519 args to create secret Signed-off-by: Stefan Prodan --- cmd/flux/bootstrap.go | 2 +- cmd/flux/create_secret.go | 30 +++++++++++++++ cmd/flux/create_secret_git.go | 41 ++++++++++++++++++-- cmd/flux/create_source_git.go | 70 +++-------------------------------- 4 files changed, 73 insertions(+), 70 deletions(-) diff --git a/cmd/flux/bootstrap.go b/cmd/flux/bootstrap.go index ddfc2ff2..0bf38de6 100644 --- a/cmd/flux/bootstrap.go +++ b/cmd/flux/bootstrap.go @@ -239,7 +239,7 @@ func shouldCreateDeployKey(ctx context.Context, kubeClient client.Client, namesp } func generateDeployKey(ctx context.Context, kubeClient client.Client, url *url.URL, namespace string) (string, error) { - pair, err := generateKeyPair(ctx) + pair, err := generateKeyPair(ctx, sourceGitKeyAlgorithm, sourceGitRSABits, sourceGitECDSACurve) if err != nil { return "", err } diff --git a/cmd/flux/create_secret.go b/cmd/flux/create_secret.go index 703c971a..e091652e 100644 --- a/cmd/flux/create_secret.go +++ b/cmd/flux/create_secret.go @@ -17,11 +17,15 @@ limitations under the License. package main import ( + "context" "fmt" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" ) @@ -35,6 +39,32 @@ func init() { createCmd.AddCommand(createSecretCmd) } +func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.Secret) error { + namespacedName := types.NamespacedName{ + Namespace: secret.GetNamespace(), + Name: secret.GetName(), + } + + var existing corev1.Secret + err := kubeClient.Get(ctx, namespacedName, &existing) + if err != nil { + if errors.IsNotFound(err) { + if err := kubeClient.Create(ctx, &secret); err != nil { + return err + } else { + return nil + } + } + return err + } + + existing.StringData = secret.StringData + if err := kubeClient.Update(ctx, &existing); err != nil { + return err + } + return nil +} + func exportSecret(secret corev1.Secret) error { secret.TypeMeta = metav1.TypeMeta{ APIVersion: "v1", diff --git a/cmd/flux/create_secret_git.go b/cmd/flux/create_secret_git.go index 31d2a2ef..65ebc9ad 100644 --- a/cmd/flux/create_secret_git.go +++ b/cmd/flux/create_secret_git.go @@ -21,6 +21,7 @@ import ( "crypto/elliptic" "fmt" "net/url" + "time" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" @@ -28,6 +29,7 @@ import ( "github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/utils" + "github.com/fluxcd/pkg/ssh" ) var createSecretGitCmd = &cobra.Command{ @@ -82,9 +84,9 @@ func init() { createSecretGitCmd.Flags().StringVar(&secretGitURL, "url", "", "git address, e.g. ssh://git@host/org/repository") createSecretGitCmd.Flags().StringVarP(&secretGitUsername, "username", "u", "", "basic authentication username") createSecretGitCmd.Flags().StringVarP(&secretGitPassword, "password", "p", "", "basic authentication password") - createSecretGitCmd.Flags().Var(&secretGitKeyAlgorithm, "ssh-key-algorithm", sourceGitKeyAlgorithm.Description()) - createSecretGitCmd.Flags().Var(&secretGitRSABits, "ssh-rsa-bits", sourceGitRSABits.Description()) - createSecretGitCmd.Flags().Var(&secretGitECDSACurve, "ssh-ecdsa-curve", sourceGitECDSACurve.Description()) + createSecretGitCmd.Flags().Var(&secretGitKeyAlgorithm, "ssh-key-algorithm", secretGitKeyAlgorithm.Description()) + createSecretGitCmd.Flags().Var(&secretGitRSABits, "ssh-rsa-bits", secretGitRSABits.Description()) + createSecretGitCmd.Flags().Var(&secretGitECDSACurve, "ssh-ecdsa-curve", secretGitECDSACurve.Description()) createSecretCmd.AddCommand(createSecretGitCmd) } @@ -122,7 +124,7 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error { switch u.Scheme { case "ssh": - pair, err := generateKeyPair(ctx) + pair, err := generateKeyPair(ctx, secretGitKeyAlgorithm, secretGitRSABits, secretGitECDSACurve) if err != nil { return err } @@ -171,3 +173,34 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error { return nil } + +func generateKeyPair(ctx context.Context, alg flags.PublicKeyAlgorithm, rsa flags.RSAKeyBits, ecdsa flags.ECDSACurve) (*ssh.KeyPair, error) { + var keyGen ssh.KeyPairGenerator + switch algorithm := alg.String(); algorithm { + case "rsa": + keyGen = ssh.NewRSAGenerator(int(rsa)) + case "ecdsa": + keyGen = ssh.NewECDSAGenerator(ecdsa.Curve) + case "ed25519": + keyGen = ssh.NewEd25519Generator() + default: + return nil, fmt.Errorf("unsupported public key algorithm: %s", algorithm) + } + pair, err := keyGen.Generate() + if err != nil { + return nil, fmt.Errorf("key pair generation failed, error: %w", err) + } + return pair, nil +} + +func scanHostKey(ctx context.Context, url *url.URL) ([]byte, error) { + host := url.Host + if url.Port() == "" { + host = host + ":22" + } + hostKey, err := ssh.ScanHostKey(host, 30*time.Second) + if err != nil { + return nil, fmt.Errorf("SSH key scan for host %s failed, error: %w", host, err) + } + return hostKey, nil +} diff --git a/cmd/flux/create_source_git.go b/cmd/flux/create_source_git.go index dd435a52..8c8c4a8f 100644 --- a/cmd/flux/create_source_git.go +++ b/cmd/flux/create_source_git.go @@ -23,13 +23,7 @@ import ( "io/ioutil" "net/url" "os" - "time" - "github.com/fluxcd/flux2/internal/flags" - "github.com/fluxcd/flux2/internal/utils" - "github.com/fluxcd/pkg/apis/meta" - - sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" "github.com/manifoldco/promptui" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" @@ -40,7 +34,10 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/fluxcd/pkg/ssh" + "github.com/fluxcd/flux2/internal/flags" + "github.com/fluxcd/flux2/internal/utils" + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" ) var createSourceGitCmd = &cobra.Command{ @@ -195,7 +192,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error { withAuth = true } else if u.Scheme == "ssh" { logger.Generatef("generating deploy key pair") - pair, err := generateKeyPair(ctx) + pair, err := generateKeyPair(ctx, sourceGitKeyAlgorithm, sourceGitRSABits, sourceGitECDSACurve) if err != nil { return err } @@ -288,63 +285,6 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error { return nil } -func generateKeyPair(ctx context.Context) (*ssh.KeyPair, error) { - var keyGen ssh.KeyPairGenerator - switch algorithm := sourceGitKeyAlgorithm.String(); algorithm { - case "rsa": - keyGen = ssh.NewRSAGenerator(int(sourceGitRSABits)) - case "ecdsa": - keyGen = ssh.NewECDSAGenerator(sourceGitECDSACurve.Curve) - case "ed25519": - keyGen = ssh.NewEd25519Generator() - default: - return nil, fmt.Errorf("unsupported public key algorithm: %s", algorithm) - } - pair, err := keyGen.Generate() - if err != nil { - return nil, fmt.Errorf("key pair generation failed, error: %w", err) - } - return pair, nil -} - -func scanHostKey(ctx context.Context, url *url.URL) ([]byte, error) { - host := url.Host - if url.Port() == "" { - host = host + ":22" - } - hostKey, err := ssh.ScanHostKey(host, 30*time.Second) - if err != nil { - return nil, fmt.Errorf("SSH key scan for host %s failed, error: %w", host, err) - } - return hostKey, nil -} - -func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.Secret) error { - namespacedName := types.NamespacedName{ - Namespace: secret.GetNamespace(), - Name: secret.GetName(), - } - - var existing corev1.Secret - err := kubeClient.Get(ctx, namespacedName, &existing) - if err != nil { - if errors.IsNotFound(err) { - if err := kubeClient.Create(ctx, &secret); err != nil { - return err - } else { - return nil - } - } - return err - } - - existing.StringData = secret.StringData - if err := kubeClient.Update(ctx, &existing); err != nil { - return err - } - return nil -} - func upsertGitRepository(ctx context.Context, kubeClient client.Client, gitRepository *sourcev1.GitRepository) (types.NamespacedName, error) { namespacedName := types.NamespacedName{ From daeb41c31b647ac9b9476b0a1dada917bb89020d Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Fri, 8 Jan 2021 11:01:40 +0100 Subject: [PATCH 19/54] Uses path instead of filepath Signed-off-by: Somtochi Onyekwere --- pkg/manifestgen/sync/sync.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/manifestgen/sync/sync.go b/pkg/manifestgen/sync/sync.go index e2d3c116..840d8498 100644 --- a/pkg/manifestgen/sync/sync.go +++ b/pkg/manifestgen/sync/sync.go @@ -19,7 +19,7 @@ package sync import ( "bytes" "fmt" - "path/filepath" + "path" "strings" "time" @@ -93,7 +93,7 @@ func Generate(options Options) (*manifestgen.Manifest, error) { } return &manifestgen.Manifest{ - Path: filepath.Join(options.TargetPath, options.Namespace, options.ManifestFile), + Path: path.Join(options.TargetPath, options.Namespace, options.ManifestFile), Content: fmt.Sprintf("---\n%s---\n%s", resourceToString(gitData), resourceToString(ksData)), }, nil } From 331ac3f03152554e17237c0a8e83ae7ecdb6f87c Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 8 Jan 2021 12:09:47 +0200 Subject: [PATCH 20/54] Add create secret helm command Signed-off-by: Stefan Prodan --- cmd/flux/create_secret_git.go | 4 +- cmd/flux/create_secret_helm.go | 141 ++++++++++++++++++++++++++++ docs/cmd/flux_create_secret.md | 1 + docs/cmd/flux_create_secret_git.md | 4 +- docs/cmd/flux_create_secret_helm.md | 65 +++++++++++++ mkdocs.yml | 1 + 6 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 cmd/flux/create_secret_helm.go create mode 100644 docs/cmd/flux_create_secret_helm.md diff --git a/cmd/flux/create_secret_git.go b/cmd/flux/create_secret_git.go index 65ebc9ad..693008ab 100644 --- a/cmd/flux/create_secret_git.go +++ b/cmd/flux/create_secret_git.go @@ -55,7 +55,7 @@ For Git over HTTP/S, the provided basic authentication credentials are stored in # Create a Git SSH secret on disk and print the deploy key flux create secret git podinfo-auth \ --url=ssh://git@github.com/stefanprodan/podinfo \ - --export > podinfo-auth.yaml + --export > podinfo-auth.yaml yq read podinfo-auth.yaml 'data."identity.pub"' | base64 --decode @@ -63,7 +63,7 @@ For Git over HTTP/S, the provided basic authentication credentials are stored in flux create secret git podinfo-auth \ --namespace=apps \ --url=ssh://git@github.com/stefanprodan/podinfo \ - --export > podinfo-auth.yaml + --export > podinfo-auth.yaml sops --encrypt --encrypted-regex '^(data|stringData)$' \ --in-place podinfo-auth.yaml diff --git a/cmd/flux/create_secret_helm.go b/cmd/flux/create_secret_helm.go new file mode 100644 index 00000000..e0b22ac6 --- /dev/null +++ b/cmd/flux/create_secret_helm.go @@ -0,0 +1,141 @@ +/* +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "io/ioutil" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/fluxcd/flux2/internal/utils" +) + +var createSecretHelmCmd = &cobra.Command{ + Use: "helm [name]", + Short: "Create or update a Kubernetes secret for Helm repository authentication", + Long: ` +The create secret helm command generates a Kubernetes secret with basic authentication credentials.`, + Example: ` # Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS + + flux create secret helm repo-auth \ + --namespace=my-namespace \ + --username=my-username \ + --password=my-password \ + --export > repo-auth.yaml + + sops --encrypt --encrypted-regex '^(data|stringData)$' \ + --in-place repo-auth.yaml + + # Create an authentication secret using a custom TLS cert + flux create secret helm repo-auth \ + --username=username \ + --password=password \ + --cert-file=./cert.crt \ + --key-file=./key.crt \ + --ca-file=./ca.crt +`, + RunE: createSecretHelmCmdRun, +} + +var ( + secretHelmUsername string + secretHelmPassword string + secretHelmCertFile string + secretHelmKeyFile string + secretHelmCAFile string +) + +func init() { + createSecretHelmCmd.Flags().StringVarP(&secretHelmUsername, "username", "u", "", "basic authentication username") + createSecretHelmCmd.Flags().StringVarP(&secretHelmPassword, "password", "p", "", "basic authentication password") + createSecretHelmCmd.Flags().StringVar(&secretHelmCertFile, "cert-file", "", "TLS authentication cert file path") + createSecretHelmCmd.Flags().StringVar(&secretHelmKeyFile, "key-file", "", "TLS authentication key file path") + createSecretHelmCmd.Flags().StringVar(&secretHelmCAFile, "ca-file", "", "TLS authentication CA file path") + + createSecretCmd.AddCommand(createSecretHelmCmd) +} + +func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("secret name is required") + } + name := args[0] + + secretLabels, err := parseLabels() + if err != nil { + return err + } + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: secretLabels, + }, + StringData: map[string]string{}, + } + + if secretHelmUsername != "" && secretHelmPassword != "" { + secret.StringData["username"] = secretHelmUsername + secret.StringData["password"] = secretHelmPassword + } + + if secretHelmCertFile != "" && secretHelmKeyFile != "" { + cert, err := ioutil.ReadFile(secretHelmCertFile) + if err != nil { + return fmt.Errorf("failed to read repository cert file '%s': %w", secretHelmCertFile, err) + } + secret.StringData["certFile"] = string(cert) + + key, err := ioutil.ReadFile(secretHelmKeyFile) + if err != nil { + return fmt.Errorf("failed to read repository key file '%s': %w", secretHelmKeyFile, err) + } + secret.StringData["keyFile"] = string(key) + } + + if secretHelmCAFile != "" { + ca, err := ioutil.ReadFile(secretHelmCAFile) + if err != nil { + return fmt.Errorf("failed to read repository CA file '%s': %w", secretHelmCAFile, err) + } + secret.StringData["caFile"] = string(ca) + } + + if export { + return exportSecret(secret) + } + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) + if err != nil { + return err + } + + if err := upsertSecret(ctx, kubeClient, secret); err != nil { + return err + } + logger.Actionf("secret '%s' created in '%s' namespace", name, namespace) + + return nil +} diff --git a/docs/cmd/flux_create_secret.md b/docs/cmd/flux_create_secret.md index 44c87f45..791bd8b4 100644 --- a/docs/cmd/flux_create_secret.md +++ b/docs/cmd/flux_create_secret.md @@ -29,4 +29,5 @@ The create source sub-commands generate Kubernetes secrets specific to Flux. * [flux create](flux_create.md) - Create or update sources and resources * [flux create secret git](flux_create_secret_git.md) - Create or update a Kubernetes secret for Git authentication +* [flux create secret helm](flux_create_secret_helm.md) - Create or update a Kubernetes secret for Helm repository authentication diff --git a/docs/cmd/flux_create_secret_git.md b/docs/cmd/flux_create_secret_git.md index f5ef66ea..09eb6fcf 100644 --- a/docs/cmd/flux_create_secret_git.md +++ b/docs/cmd/flux_create_secret_git.md @@ -32,7 +32,7 @@ flux create secret git [name] [flags] # Create a Git SSH secret on disk and print the deploy key flux create secret git podinfo-auth \ --url=ssh://git@github.com/stefanprodan/podinfo \ - --export > podinfo-auth.yaml + --export > podinfo-auth.yaml yq read podinfo-auth.yaml 'data."identity.pub"' | base64 --decode @@ -40,7 +40,7 @@ flux create secret git [name] [flags] flux create secret git podinfo-auth \ --namespace=apps \ --url=ssh://git@github.com/stefanprodan/podinfo \ - --export > podinfo-auth.yaml + --export > podinfo-auth.yaml sops --encrypt --encrypted-regex '^(data|stringData)$' \ --in-place podinfo-auth.yaml diff --git a/docs/cmd/flux_create_secret_helm.md b/docs/cmd/flux_create_secret_helm.md new file mode 100644 index 00000000..e01bad38 --- /dev/null +++ b/docs/cmd/flux_create_secret_helm.md @@ -0,0 +1,65 @@ +## flux create secret helm + +Create or update a Kubernetes secret for Helm repository authentication + +### Synopsis + + +The create secret helm command generates a Kubernetes secret with basic authentication credentials. + +``` +flux create secret helm [name] [flags] +``` + +### Examples + +``` + # Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS + + flux create secret helm repo-auth \ + --namespace=my-namespace \ + --username=my-username \ + --password=my-password \ + --export > repo-auth.yaml + + sops --encrypt --encrypted-regex '^(data|stringData)$' \ + --in-place repo-auth.yaml + + # Create an authentication secret using a custom TLS cert + flux create secret helm repo-auth \ + --username=username \ + --password=password \ + --cert-file=./cert.crt \ + --key-file=./key.crt \ + --ca-file=./ca.crt + +``` + +### Options + +``` + --ca-file string TLS authentication CA file path + --cert-file string TLS authentication cert file path + -h, --help help for helm + --key-file string TLS authentication key file path + -p, --password string basic authentication password + -u, --username string basic authentication username +``` + +### Options inherited from parent commands + +``` + --context string kubernetes context to use + --export export in YAML format to stdout + --interval duration source sync interval (default 1m0s) + --kubeconfig string path to the kubeconfig file (default "~/.kube/config") + --label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2) + -n, --namespace string the namespace scope for this operation (default "flux-system") + --timeout duration timeout for this operation (default 5m0s) + --verbose print generated objects +``` + +### SEE ALSO + +* [flux create secret](flux_create_secret.md) - Create or update Kubernetes secrets + diff --git a/mkdocs.yml b/mkdocs.yml index c0881230..f5d3cf9c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -101,6 +101,7 @@ nav: - Create tenant: cmd/flux_create_tenant.md - Create secret: cmd/flux_create_secret.md - Create secret git: cmd/flux_create_secret_git.md + - Create secret helm: cmd/flux_create_secret_helm.md - Delete: cmd/flux_delete.md - Delete kustomization: cmd/flux_delete_kustomization.md - Delete helmrelease: cmd/flux_delete_helmrelease.md From d03944893d0f126a7dc78c7dacc1995c566573ef Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 8 Jan 2021 13:12:07 +0200 Subject: [PATCH 21/54] Add DCO signoff ref to contributing doc Signed-off-by: Stefan Prodan --- CONTRIBUTING.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5a5b7b07..cbd76fa9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,6 @@ # Contributing -Flux is [Apache 2.0 -licensed](https://github.com/fluxcd/flux2/blob/main/LICENSE) and +Flux is [Apache 2.0 licensed](https://github.com/fluxcd/flux2/blob/main/LICENSE) and accepts contributions via GitHub pull requests. This document outlines some of the conventions on to make it easier to get your contribution accepted. @@ -14,9 +13,18 @@ code. By contributing to this project you agree to the Developer Certificate of Origin (DCO). This document was created by the Linux Kernel community and is a simple statement that you, as a contributor, have the legal right to make the -contribution. No action from you is required, but it's a good idea to see the -[DCO](DCO) file for details before you start contributing code to FluxCD -organization. +contribution. + +We require all commits to be signed. By signing off with your signature, you +certify that you wrote the patch or otherwise have the right to contribute the +material by the rules of the [DCO](DCO): + +`Signed-off-by: Jane Doe ` + +The signature must contain your real name +(sorry, no pseudonyms or anonymous contributions) +If your `user.name` and `user.email` are configured in your Git config, +you can sign your commit automatically with `git commit -s`. ## Communications From eba2dd36e037a7764ff17d80b7b3204c791eb58e Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Wed, 23 Dec 2020 13:15:46 +0100 Subject: [PATCH 22/54] Update getting started guide Signed-off-by: Somtochi Onyekwere --- docs/get-started/index.md | 264 +++++++++++--------------------------- 1 file changed, 75 insertions(+), 189 deletions(-) diff --git a/docs/get-started/index.md b/docs/get-started/index.md index ef3c1a58..7a4fe5cb 100644 --- a/docs/get-started/index.md +++ b/docs/get-started/index.md @@ -1,14 +1,20 @@ # Get started with Flux v2 +!!! note "Basic knowledge" + This guide assumes you have some understanding of the core concepts and have read the introduction to Flux. + The core concepts used in this guide are [GitOps](../core-concepts/index.md#gitops), [Sources](../core-concepts/index.md#sources), [Kustomization](../core-concepts/index.md#kustomization). + +In this tutorial, you will deploy an application to a kubernetes cluster with Flux and manage the cluster in a complete GitOps manner. You'll be using a dedicated Git repository e.g. `fleet-infra` to manage the Kubernetes clusters. All the manifest will be pushed to this repository and then applied by Flux. + ## Prerequisites -You will need two Kubernetes clusters version 1.16 or newer and kubectl version 1.18. +In order to follow the guide, you will need one Kubernetes cluster version 1.16 or newer and kubectl version 1.18. For a quick local test, you can use [Kubernetes kind](https://kind.sigs.k8s.io/docs/user/quick-start/). Any other Kubernetes setup will work as well though. -In order to follow the guide you'll need a GitHub account and a +Flux is installed in a complete GitOps way and its manifest will be pushed to the repository, so you will also need a GitHub account and a [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) -that can create repositories (check all permissions under `repo`). +that can create repositories (check all permissions under `repo`) to enable Flux do this. Export your GitHub personal access token and username: @@ -52,24 +58,13 @@ profile: `zsh`, `fish`, and `powershell` are also supported with their own sub-commands. -## GitOps workflow - -You'll be using a dedicated Git repository e.g. `fleet-infra` to manage one or more Kubernetes clusters. -This guide assumes that you have two clusters, one for staging and one for production. - -Using the Flux CLI you'll do the following: - -- configure each cluster to synchronise with a directory inside the fleet repository -- register app sources (git repositories) that contain plain Kubernetes manifests or Kustomize overlays -- configure app deployments on both clusters (pre-releases on staging, semver releases on production) +## Install Flux components -## Staging bootstrap - -Create the staging cluster using Kubernetes kind or set the kubectl context to an existing cluster: +Create the cluster using Kubernetes kind or set the kubectl context to an existing cluster: ```sh -kind create cluster --name staging -kubectl cluster-info --context kind-staging +kind create cluster +kubectl cluster-info --context kind-kind ``` Verify that your staging cluster satisfies the prerequisites with: @@ -89,7 +84,7 @@ flux bootstrap github \ --owner=$GITHUB_USER \ --repository=fleet-infra \ --branch=main \ - --path=staging-cluster \ + --path=./clusters/my-cluster \ --personal ``` @@ -98,8 +93,8 @@ flux bootstrap github \ you can use `--arch=arm` for ARMv7 32-bit container images and `--arch=arm64` for ARMv8 64-bit container images. -The bootstrap command creates a repository if one doesn't exist, and -commits the manifests for the Flux components to the default branch at the specified path. +The bootstrap command creates a repository if one doesn't exist, +commits the manifests for the Flux components to the default branch at the specified path, and installs the Flux components. Then it configures the target cluster to synchronize with the specified path inside the repository. If you wish to create the repository under a GitHub organization: @@ -111,7 +106,7 @@ flux bootstrap github \ --branch= \ --team= \ --team= \ - --path=staging-cluster + --path=./clusters/my-cluster ``` Example output: @@ -148,102 +143,104 @@ If you prefer GitLab, export `GITLAB_TOKEN` env var and use the command [flux bo You can target a specific Flux [version](https://github.com/fluxcd/flux2/releases) with `flux bootstrap --version=`. -## Staging workflow +## Clone the git repository + +We are going to be managing the application in a GitOps manner with the git repository. The Flux manifests generated by the CLI will be pushed to the git repository. Instead of applying the manifests directly to the cluster, Flux will apply it for us instead :). -Clone the repository with: +Therefore, we need to clone the repository to our local machine. ```sh git clone https://github.com/$GITHUB_USER/fleet-infra cd fleet-infra ``` -Create a git source pointing to a public repository master branch: + +## Add podinfo repository to Flux + +We will be using a public repository [github.com/stefanprodan/podinfo](https://github.com/stefanprodan/podinfo), podinfo is a tiny web application made with Go. + +Create a GitRepository manifest pointing to the repository's master branch with Flux CLI. ```sh -flux create source git webapp \ +flux create source git podinfo \ --url=https://github.com/stefanprodan/podinfo \ --branch=master \ --interval=30s \ - --export > ./staging-cluster/webapp-source.yaml + --export > ./clusters/my-cluster/podinfo-source.yaml ``` -Create a kustomization for synchronizing the common manifests on the cluster: +Commit and push it to the `fleet-infra` repository, then Flux applies it to the cluster. ```sh -flux create kustomization webapp-common \ - --source=webapp \ - --path="./deploy/webapp/common" \ - --prune=true \ - --validation=client \ - --interval=1h \ - --export > ./staging-cluster/webapp-common.yaml +git add -A && git commit -m "Adds podinfo git source" +git push ``` -Create a kustomization for the backend service that depends on common: +## Deploy podinfo application + +We will create a kustomization manifest for podinfo. This will apply the manifest in the `kustomize` directory in the podinfo repository. ```sh -flux create kustomization webapp-backend \ - --depends-on=webapp-common \ - --source=webapp \ - --path="./deploy/webapp/backend" \ +flux create kustomization podinfo \ + --source=podinfo \ + --path="./kustomize" \ --prune=true \ --validation=client \ - --interval=10m \ - --health-check="Deployment/backend.webapp" \ - --health-check-timeout=2m \ - --export > ./staging-cluster/webapp-backend.yaml + --interval=5m \ + --export > ./clusters/my-cluster/podinfo-kustomization.yaml ``` -Create a kustomization for the frontend service that depends on backend: +Commit and push the kustomization manifest to the git repository so that Flux applies it to the cluster. ```sh -flux create kustomization webapp-frontend \ - --depends-on=webapp-backend \ - --source=webapp \ - --path="./deploy/webapp/frontend" \ - --prune=true \ - --validation=client \ - --interval=10m \ - --health-check="Deployment/frontend.webapp" \ - --health-check-timeout=2m \ - --export > ./staging-cluster/webapp-frontend.yaml +git add -A && git commit -m "Adds podinfo kustomization" +git push ``` -Push changes to origin: - -```sh -git add -A && git commit -m "add staging webapp" && git push +The structure of your repository should look like this: +``` +fleet-infra +└── clusters/ + └── my-cluster/ + ├── flux-system/ + │ ├── gotk-components.yaml/ + │ ├── gotk-sync.yaml/ + │ └── kustomization.yaml/ + ├── podinfo-kustomization.yaml + └── podinfo-source.yaml ``` + +## Watch Flux sync the application + In about 30s the synchronization should start: ```console $ watch flux get kustomizations NAME READY MESSAGE -flux-system True Applied revision: main/6eea299fe9997c8561b826b67950afaf9a476cf8 -webapp-backend False dependency 'flux-system/webapp-common' is not ready -webapp-common True Applied revision: master/7411da595c25183daba255068814b83843fe3395 -webapp-frontend False dependency 'flux-system/webapp-backend' is not ready +flux-system main/fc07af652d3168be329539b30a4c3943a7d12dd8 False True Applied revision: main/fc07af652d3168be329539b30a4c3943a7d12dd8 +podinfo master/855f7724be13f6146f61a893851522837ad5b634 False True Applied revision: master/855f7724be13f6146f61a893851522837ad5b634 ``` -When the synchronization finishes you can check that the webapp services are running: +When the synchronization finishes you can check that the podinfo has been deployed on your cluster: ```console -$ kubectl -n webapp get deployments,services -NAME READY UP-TO-DATE AVAILABLE AGE -deployment.apps/backend 1/1 1 1 4m1s -deployment.apps/frontend 1/1 1 1 3m31s - -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -service/backend ClusterIP 10.52.10.22 9898/TCP,9999/TCP 4m1s -service/frontend ClusterIP 10.52.9.85 80/TCP 3m31s +$ kubectl -n default get deployments,services +NAME READY UP-TO-DATE AVAILABLE AGE +deployment.apps/podinfo 2/2 2 2 108s + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/podinfo ClusterIP 10.100.149.126 9898/TCP,9999/TCP 108s ``` !!! tip From this moment forward, any changes made to the webapp - Kubernetes manifests in the master branch will be synchronised with the staging cluster. + Kubernetes manifests in the master branch will be synchronised with your cluster. If a Kubernetes manifest is removed from the webapp repository, the reconciler will remove it from your cluster. +If you delete a kustomization from the cluster, the reconciler will remove all Kubernetes objects that +were previously applied from that kustomization. + If you delete a kustomization from the `fleet-infra` repo, the reconciler will remove all Kubernetes objects that were previously applied from that kustomization. @@ -252,121 +249,10 @@ the state described in git. When dealing with an incident, you can pause the rec kustomization with `flux suspend kustomization `. Once the debugging session is over, you can re-enable the reconciliation with `flux resume kustomization `. -## Production bootstrap - -On production clusters, you may wish to deploy stable releases of an application. -When creating a git source instead of a branch, you can specify a git tag or a semver expression. - -Create the production cluster using Kubernetes kind or set the kubectl context to an existing cluster: - -```sh -kind create cluster --name production -kubectl cluster-info --context kind-production -``` - -Run the bootstrap for the production environment: - -```sh -flux bootstrap github \ - --owner=$GITHUB_USER \ - --repository=fleet-infra \ - --path=prod-cluster \ - --personal -``` -Pull the changes locally: - -```sh -git pull -``` - -## Production workflow +## Multi-cluster Setup -Create a git source using a semver range to target stable releases: +To use Flux to manage more than one cluster or promote deployments from staging to production, take a look at the +two approaches in the repositories listed below. -```sh -flux create source git webapp \ - --url=https://github.com/stefanprodan/podinfo \ - --tag-semver=">=4.0.0 <4.0.2" \ - --interval=30s \ - --export > ./prod-cluster/webapp-source.yaml -``` - -Create a kustomization for webapp pointing to the production overlay: - -```sh -flux create kustomization webapp \ - --source=webapp \ - --path="./deploy/overlays/production" \ - --prune=true \ - --validation=client \ - --interval=10m \ - --health-check="Deployment/frontend.production" \ - --health-check="Deployment/backend.production" \ - --health-check-timeout=2m \ - --export > ./prod-cluster/webapp-production.yaml -``` - -Push changes to origin: - -```sh -git add -A && git commit -m "add prod webapp" && git push -``` - -List git sources: - -```console -$ flux get sources git -NAME REVISION READY MESSAGE -flux-system main/5ae055e24b2c8a78f981708b61507a97a30bd7a6 True Fetched revision: main/113360052b3153e439a0cf8de76b8e3d2a7bdf27 -webapp 4.0.1/113360052b3153e439a0cf8de76b8e3d2a7bdf27 True Fetched revision: 4.0.1/113360052b3153e439a0cf8de76b8e3d2a7bdf27 -``` - -The kubectl equivalent is `kubectl -n flux-system get gitrepositories`. - -List kustomization: - -```console -$ flux get kustomizations -NAME REVISION SUSPENDED READY MESSAGE -flux-system main/5ae055e24b2c8a78f981708b61507a97a30bd7a6 False True Applied revision: main/5ae055e24b2c8a78f981708b61507a97a30bd7a6 -webapp 4.0.1/113360052b3153e439a0cf8de76b8e3d2a7bdf27 False True Applied revision: 4.0.1/113360052b3153e439a0cf8de76b8e3d2a7bdf27 -``` - -The kubectl equivalent is `kubectl -n flux-system get kustomizations`. - -If you want to upgrade to the latest 4.x version, you can change the semver expression to: - -```sh -flux create source git webapp \ - --url=https://github.com/stefanprodan/podinfo \ - --tag-semver=">=4.0.0 <5.0.0" \ - --interval=30s \ - --export > ./prod-cluster/webapp-source.yaml - -git add -A && git commit -m "update prod webapp" && git push -``` - -Trigger a git sync: - -```console -$ flux reconcile ks flux-system --with-source -► annotating source flux-system -✔ source annotated -◎ waiting for reconcilitation -✔ git reconciliation completed -✔ fetched revision main/d751ea264d48bf0db8b588d1d08184834ac8fec9 -◎ waiting for kustomization reconcilitation -✔ kustomization reconcilitation completed -✔ applied revision main/d751ea264d48bf0db8b588d1d08184834ac8fec9 -``` - -The kubectl equivalent is `kubectl -n flux-system annotate gitrepository/flux-system fluxcd.io/reconcileAt="$(date +%s)"`. - -Wait for the webapp to be upgraded: - -```console -$ watch flux get kustomizations -NAME REVISION SUSPENDED READY MESSAGE -flux-system main/d751ea264d48bf0db8b588d1d08184834ac8fec9 False True Applied revision: main/d751ea264d48bf0db8b588d1d08184834ac8fec9 -webapp 4.0.6/26a630c0b4b3452833d96c511d93f6f2d2e90a99 False True Applied revision: 4.0.6/26a630c0b4b3452833d96c511d93f6f2d2e90a99 -``` +1. [https://github.com/fluxcd/flux2-kustomize-helm-example](https://github.com/fluxcd/flux2-kustomize-helm-example) +2. [https://github.com/fluxcd/flux2-multi-tenancy](https://github.com/fluxcd/flux2-multi-tenancy) \ No newline at end of file From 0aeb3128ed4f1c236c9f5537936dd0e75f3084ba Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 8 Jan 2021 17:45:31 +0200 Subject: [PATCH 23/54] Fix semver range prerelease examples Signed-off-by: Stefan Prodan --- docs/guides/image-update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/image-update.md b/docs/guides/image-update.md index 54540652..e0bd53b4 100644 --- a/docs/guides/image-update.md +++ b/docs/guides/image-update.md @@ -183,7 +183,7 @@ spec: A semver range that includes stable releases can be defined with `1.0.x` (patch versions only) or `>=1.0.0 <2.0.0` (minor and patch versions). If you want to include pre-release e.g. `1.0.0-rc.1`, - you can define a range like: `>1.0.0-rc <2.0.0`. + you can define a range like: `^1.x-0` or `>1.0.0-rc <2.0.0-rc`. Commit and push changes to main branch: From dcb505045e7e4b604ed0a6b5f022207edae208ed Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 8 Jan 2021 17:46:00 +0200 Subject: [PATCH 24/54] Add generated manifests to get started guide Signed-off-by: Stefan Prodan --- docs/get-started/index.md | 110 ++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 34 deletions(-) diff --git a/docs/get-started/index.md b/docs/get-started/index.md index 7a4fe5cb..a038be65 100644 --- a/docs/get-started/index.md +++ b/docs/get-started/index.md @@ -2,17 +2,21 @@ !!! note "Basic knowledge" This guide assumes you have some understanding of the core concepts and have read the introduction to Flux. - The core concepts used in this guide are [GitOps](../core-concepts/index.md#gitops), [Sources](../core-concepts/index.md#sources), [Kustomization](../core-concepts/index.md#kustomization). + The core concepts used in this guide are [GitOps](../core-concepts/index.md#gitops), + [Sources](../core-concepts/index.md#sources), [Kustomization](../core-concepts/index.md#kustomization). -In this tutorial, you will deploy an application to a kubernetes cluster with Flux and manage the cluster in a complete GitOps manner. You'll be using a dedicated Git repository e.g. `fleet-infra` to manage the Kubernetes clusters. All the manifest will be pushed to this repository and then applied by Flux. +In this tutorial, you will deploy an application to a kubernetes cluster with Flux +and manage the cluster in a complete GitOps manner. +You'll be using a dedicated Git repository e.g. `fleet-infra` to manage your Kubernetes clusters. ## Prerequisites -In order to follow the guide, you will need one Kubernetes cluster version 1.16 or newer and kubectl version 1.18. +In order to follow the guide, you will need a Kubernetes cluster version 1.16 or newer and kubectl version 1.18. For a quick local test, you can use [Kubernetes kind](https://kind.sigs.k8s.io/docs/user/quick-start/). Any other Kubernetes setup will work as well though. -Flux is installed in a complete GitOps way and its manifest will be pushed to the repository, so you will also need a GitHub account and a +Flux is installed in a GitOps way and its manifest will be pushed to the repository, +so you will also need a GitHub account and a [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) that can create repositories (check all permissions under `repo`) to enable Flux do this. @@ -64,7 +68,7 @@ Create the cluster using Kubernetes kind or set the kubectl context to an existi ```sh kind create cluster -kubectl cluster-info --context kind-kind +kubectl cluster-info ``` Verify that your staging cluster satisfies the prerequisites with: @@ -94,7 +98,8 @@ flux bootstrap github \ and `--arch=arm64` for ARMv8 64-bit container images. The bootstrap command creates a repository if one doesn't exist, -commits the manifests for the Flux components to the default branch at the specified path, and installs the Flux components. +commits the manifests for the Flux components to the default branch at the specified path, +and installs the Flux components. Then it configures the target cluster to synchronize with the specified path inside the repository. If you wish to create the repository under a GitHub organization: @@ -112,7 +117,7 @@ flux bootstrap github \ Example output: ```console -$ flux bootstrap github --owner=gitopsrun --repository=fleet-infra --path=staging-cluster --team=devs +$ flux bootstrap github --owner=gitopsrun --team=devs --repository=fleet-infra --path=./clusters/my-cluster ► connecting to github.com ✔ repository created ✔ devs team access granted @@ -134,7 +139,8 @@ deployment "notification-controller" successfully rolled out ✔ bootstrap finished ``` -If you prefer GitLab, export `GITLAB_TOKEN` env var and use the command [flux bootstrap gitlab](../cmd/flux_bootstrap_gitlab.md). +If you prefer GitLab, export `GITLAB_TOKEN` env var and +use the command [flux bootstrap gitlab](../guides/installation.md#gitlab-and-gitlab-enterprise). !!! hint "Idempotency" It is safe to run the bootstrap command as many times as you want. @@ -145,21 +151,25 @@ If you prefer GitLab, export `GITLAB_TOKEN` env var and use the command [flux bo ## Clone the git repository -We are going to be managing the application in a GitOps manner with the git repository. The Flux manifests generated by the CLI will be pushed to the git repository. Instead of applying the manifests directly to the cluster, Flux will apply it for us instead :). +We are going to drive app deployments in a GitOps manner, +using the Git repository as the desired state for our cluster. +Instead of applying the manifests directly to the cluster, +Flux will apply it for us instead. -Therefore, we need to clone the repository to our local machine. +Therefore, we need to clone the repository to our local machine: ```sh git clone https://github.com/$GITHUB_USER/fleet-infra cd fleet-infra ``` - ## Add podinfo repository to Flux -We will be using a public repository [github.com/stefanprodan/podinfo](https://github.com/stefanprodan/podinfo), podinfo is a tiny web application made with Go. +We will be using a public repository [github.com/stefanprodan/podinfo](https://github.com/stefanprodan/podinfo), +podinfo is a tiny web application made with Go. -Create a GitRepository manifest pointing to the repository's master branch with Flux CLI. +Create a [GitRepository](../components/source/gitrepositories/) +manifest pointing to podinfo repository's master branch: ```sh flux create source git podinfo \ @@ -169,16 +179,33 @@ flux create source git podinfo \ --export > ./clusters/my-cluster/podinfo-source.yaml ``` -Commit and push it to the `fleet-infra` repository, then Flux applies it to the cluster. +The above command generates the following manifest: + +```yaml +apiVersion: source.toolkit.fluxcd.io/v1beta1 +kind: GitRepository +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 30s + ref: + branch: master + url: https://github.com/stefanprodan/podinfo +``` + +Commit and push it to the `fleet-infra` repository: ```sh -git add -A && git commit -m "Adds podinfo git source" +git add -A && git commit -m "Add podinfo GitRepository" git push ``` ## Deploy podinfo application -We will create a kustomization manifest for podinfo. This will apply the manifest in the `kustomize` directory in the podinfo repository. +We will create a Flux [Kustomization](../components/kustomize/kustomization/) manifest for podinfo. +This configures Flux to build and apply the [kustomize](https://github.com/stefanprodan/podinfo/tree/master/kustomize) +directory located in the podinfo repository. ```sh flux create kustomization podinfo \ @@ -190,27 +217,45 @@ flux create kustomization podinfo \ --export > ./clusters/my-cluster/podinfo-kustomization.yaml ``` -Commit and push the kustomization manifest to the git repository so that Flux applies it to the cluster. +The above command generates the following manifest: + +```yaml +apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 +kind: Kustomization +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 5m0s + path: ./kustomize + prune: true + sourceRef: + kind: GitRepository + name: podinfo + validation: client +``` + +Commit and push the `Kustomization` manifest to the repository: ```sh -git add -A && git commit -m "Adds podinfo kustomization" +git add -A && git commit -m "Add podinfo Kustomization" git push ``` The structure of your repository should look like this: + ``` fleet-infra └── clusters/ └── my-cluster/ ├── flux-system/ - │ ├── gotk-components.yaml/ - │ ├── gotk-sync.yaml/ - │ └── kustomization.yaml/ + │ ├── gotk-components.yaml + │ ├── gotk-sync.yaml + │ └── kustomization.yaml ├── podinfo-kustomization.yaml └── podinfo-source.yaml ``` - ## Watch Flux sync the application In about 30s the synchronization should start: @@ -218,11 +263,11 @@ In about 30s the synchronization should start: ```console $ watch flux get kustomizations NAME READY MESSAGE -flux-system main/fc07af652d3168be329539b30a4c3943a7d12dd8 False True Applied revision: main/fc07af652d3168be329539b30a4c3943a7d12dd8 -podinfo master/855f7724be13f6146f61a893851522837ad5b634 False True Applied revision: master/855f7724be13f6146f61a893851522837ad5b634 +flux-system True Applied revision: main/fc07af652d3168be329539b30a4c3943a7d12dd8 +podinfo True Applied revision: master/855f7724be13f6146f61a893851522837ad5b634 ``` -When the synchronization finishes you can check that the podinfo has been deployed on your cluster: +When the synchronization finishes you can check that podinfo has been deployed on your cluster: ```console $ kubectl -n default get deployments,services @@ -234,18 +279,15 @@ service/podinfo ClusterIP 10.100.149.126 9898/TCP,9999/TC ``` !!! tip - From this moment forward, any changes made to the webapp + From this moment forward, any changes made to the podinfo Kubernetes manifests in the master branch will be synchronised with your cluster. -If a Kubernetes manifest is removed from the webapp repository, the reconciler will remove it from your cluster. -If you delete a kustomization from the cluster, the reconciler will remove all Kubernetes objects that -were previously applied from that kustomization. - -If you delete a kustomization from the `fleet-infra` repo, the reconciler will remove all Kubernetes objects that -were previously applied from that kustomization. +If a Kubernetes manifest is removed from the podinfo repository, Flux will remove it from your cluster. +If you delete a `Kustomization` from the fleet-infra repository, Flux will remove all Kubernetes objects that +were previously applied from that `Kustomization`. -If you alter the webapp deployment using `kubectl edit`, the changes will be reverted to match -the state described in git. When dealing with an incident, you can pause the reconciliation of a +If you alter the podinfo deployment using `kubectl edit`, the changes will be reverted to match +the state described in Git. When dealing with an incident, you can pause the reconciliation of a kustomization with `flux suspend kustomization `. Once the debugging session is over, you can re-enable the reconciliation with `flux resume kustomization `. From 3697a5e3486cd98f845206aa367a379d44935d9e Mon Sep 17 00:00:00 2001 From: Stacey Potter <50154848+staceypotter@users.noreply.github.com> Date: Sun, 10 Jan 2021 14:07:09 -0500 Subject: [PATCH 25/54] Added Jan 25 Meetup to Upcoming Events section Signed-off-by: Stacey Potter <50154848+staceypotter@users.noreply.github.com> --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index 544d8dfd..45c616a8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -86,6 +86,7 @@ Depending on what you want to do, some of the following bits might be your first ### Upcoming Events - 11 Jan 2021 - [Helm + GitOps = ⚡️⚡️⚡️ with Scott Rigby](https://www.meetup.com/GitOps-Community/events/275348736/) +- 25 Jan 2021 - [GitOps Core Concepts & How to Teach Your Teams with Leigh Capili](https://www.meetup.com/GitOps-Community/events/275625806/) ### Featured Talks - 14 Dec 2020 - [The Power of GitOps with Flux and Flagger (GitOps Hands-On) with Leigh Capili](https://youtu.be/cB7iXeNLteE) From 4fcf93306a170ad17d6151583ef17099a53cd8d4 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Mon, 11 Jan 2021 15:02:02 +0100 Subject: [PATCH 26/54] Gets actual path for owner Signed-off-by: Somtochi Onyekwere --- cmd/flux/bootstrap_gitlab.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go index 1d387ba6..c2b0b609 100644 --- a/cmd/flux/bootstrap_gitlab.go +++ b/cmd/flux/bootstrap_gitlab.go @@ -129,18 +129,22 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("cluster already bootstrapped to %v path", usedPath) } - repository, err := git.NewRepository(glRepository, glOwner, glHostname, glToken, "flux", glOwner+"@users.noreply.gitlab.com") + provider := &git.GitLabProvider{ + IsPrivate: glPrivate, + IsPersonal: glPersonal, + } + owner, err := provider.GetRepositoryOwner(ctx, glToken, glOwner) if err != nil { return err } - if glSSHHostname != "" { - repository.SSHHost = glSSHHostname + repository, err := git.NewRepository(glRepository, owner, glHostname, glToken, "flux", glOwner+"@users.noreply.gitlab.com") + if err != nil { + return err } - provider := &git.GitLabProvider{ - IsPrivate: glPrivate, - IsPersonal: glPersonal, + if glSSHHostname != "" { + repository.SSHHost = glSSHHostname } tmpDir, err := ioutil.TempDir("", namespace) From 4b63ccf1403be753a93466feafd45103aefdbbbd Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Mon, 11 Jan 2021 16:17:07 +0100 Subject: [PATCH 27/54] Update fluxcd/pkg/git Signed-off-by: Somtochi Onyekwere --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 2054cbd7..3ed9c30a 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/fluxcd/kustomize-controller/api v0.5.3 github.com/fluxcd/notification-controller/api v0.5.0 github.com/fluxcd/pkg/apis/meta v0.5.0 - github.com/fluxcd/pkg/git v0.1.0 + github.com/fluxcd/pkg/git v0.2.0 github.com/fluxcd/pkg/runtime v0.4.0 github.com/fluxcd/pkg/ssh v0.0.5 github.com/fluxcd/pkg/untar v0.0.5 diff --git a/go.sum b/go.sum index 1071becb..f913809b 100644 --- a/go.sum +++ b/go.sum @@ -187,8 +187,8 @@ github.com/fluxcd/notification-controller/api v0.5.0 h1:xKKFnPVsYl2+GEjgKz5a5Mq6 github.com/fluxcd/notification-controller/api v0.5.0/go.mod h1:yLd+nrCJUThSkt4U+LLv8TRxqZdR11+gE1S2/bhgqmE= github.com/fluxcd/pkg/apis/meta v0.5.0 h1:FaU++mQY0g4sVVl+hG+vk0CXBLbb4EVfRuzs3IjLXvo= github.com/fluxcd/pkg/apis/meta v0.5.0/go.mod h1:aEUuZIawboAAFLlYz/juVJ7KNmlWbBtJFYkOWWmGUR4= -github.com/fluxcd/pkg/git v0.1.0 h1:WtQSoR1SlsI8IsiJGiFzbH9IyxFZPsTtxraAp68/H+U= -github.com/fluxcd/pkg/git v0.1.0/go.mod h1:AO+smmdF7X+ciTypdoMOTbAHeiYCCSaa56OfW0Xo064= +github.com/fluxcd/pkg/git v0.2.0 h1:EFFa8ud+9OgEQ7IhyG1pV9+jsHycBtDEHal5odjuvMM= +github.com/fluxcd/pkg/git v0.2.0/go.mod h1:8v0QVumu1ugMG3nJL0KMYPZgmLjzesJHA2sOtXAHLPA= github.com/fluxcd/pkg/runtime v0.4.0 h1:d/1okReK7ZyrQ2k/GKY1BEiMZNHu1rWKUxlHx2O45EY= github.com/fluxcd/pkg/runtime v0.4.0/go.mod h1:0Rbkgh3qj8Dl4uitccLc13hZyet1vvNJCAFAVUwNZDM= github.com/fluxcd/pkg/ssh v0.0.5 h1:rnbFZ7voy2JBlUfMbfyqArX2FYaLNpDhccGFC3qW83A= @@ -655,8 +655,8 @@ github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV github.com/vdemeester/k8s-pkg-credentialprovider v1.18.1-0.20201019120933-f1d16962a4db/go.mod h1:grWy0bkr1XO6hqbaaCKaPXqkBVlMGHYG6PGykktwbJc= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= -github.com/xanzy/go-gitlab v0.32.1 h1:eKGfAP2FWbqStD7DtGoRBb18IYwjuCxdtEVea2rNge4= -github.com/xanzy/go-gitlab v0.32.1/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= +github.com/xanzy/go-gitlab v0.38.2 h1:FF4WgwFsLfOC4Wl67c9UDIC73C+UaYJ0pkZ2irbSu4M= +github.com/xanzy/go-gitlab v0.38.2/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= From 65d0f3569cfb31b5896af26333f446bfb058c47d Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 11 Jan 2021 19:51:00 +0200 Subject: [PATCH 28/54] Fix Azure DevOps URL in docs Signed-off-by: Stefan Prodan --- docs/guides/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/installation.md b/docs/guides/installation.md index cc057257..015e7779 100644 --- a/docs/guides/installation.md +++ b/docs/guides/installation.md @@ -320,7 +320,7 @@ If you don't specify the SSH algorithm, then `flux` will generate an RSA 2048 bi ```sh flux create source git flux-system \ --git-implementation=libgit2 \ - --url=git@ssh.dev.azure.com/v3/org/project/repository \ + --url=ssh://git@ssh.dev.azure.com/v3/org/project/repository \ --branch=master \ --interval=1m ``` From 604773e866fcf1e4ba6d67d6a8157c687d10321a Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Mon, 11 Jan 2021 16:49:40 +0100 Subject: [PATCH 29/54] check for multiple files in KUBECONFIG variable Signed-off-by: Somtochi Onyekwere --- internal/utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 8eb3f239..08b81451 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -67,7 +67,7 @@ const ( func ExecKubectlCommand(ctx context.Context, mode ExecMode, kubeConfigPath string, kubeContext string, args ...string) (string, error) { var stdoutBuf, stderrBuf bytes.Buffer - if kubeConfigPath != "" { + if kubeConfigPath != "" && len(filepath.SplitList(kubeConfigPath)) == 1 { args = append(args, "--kubeconfig="+kubeConfigPath) } From dd2c20b225a39cd217c376ae6d7e335e1cc9e477 Mon Sep 17 00:00:00 2001 From: Guillaume Le Biller Date: Tue, 12 Jan 2021 15:02:29 +0100 Subject: [PATCH 30/54] Update sealed-secrets chart URL Signed-off-by: Guillaume Le Biller --- docs/guides/sealed-secrets.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guides/sealed-secrets.md b/docs/guides/sealed-secrets.md index e6345b32..5bd05c1a 100644 --- a/docs/guides/sealed-secrets.md +++ b/docs/guides/sealed-secrets.md @@ -34,9 +34,9 @@ the sealed-secrets controller from its [Helm chart](https://hub.kubeapps.com/cha First you have to register the Helm repository where the sealed-secrets chart is published: ```sh -flux create source helm stable \ +flux create source helm sealed-secrets \ --interval=1h \ ---url=https://charts.helm.sh/stable +--url=https://bitnami-labs.github.io/sealed-secrets ``` With `interval` we configure [source-controller](../components/source/controller.md) to download @@ -50,7 +50,7 @@ flux create helmrelease sealed-secrets \ --interval=1h \ --release-name=sealed-secrets \ --target-namespace=flux-system \ ---source=HelmRepository/stable \ +--source=HelmRepository/sealed-secrets \ --chart=sealed-secrets \ --chart-version="1.10.x" ``` From 479b4b5859e1cb7bf5599bba3ae1c5fa218ea813 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Fri, 8 Jan 2021 00:37:27 +0100 Subject: [PATCH 31/54] Update commit status notification guide Signed-off-by: Philip Laine --- docs/_files/commit-status-flow.png | Bin 0 -> 39347 bytes docs/_files/commit-status-github-failure.png | Bin 0 -> 18152 bytes docs/_files/commit-status-github-overview.png | Bin 0 -> 16958 bytes docs/_files/commit-status-github-success.png | Bin 0 -> 19124 bytes docs/_files/github-commit-status.png | Bin 11390 -> 0 bytes docs/_files/gitlab-commit-status.png | Bin 3435 -> 0 bytes docs/guides/notifications.md | 242 +++++++++++++++--- 7 files changed, 210 insertions(+), 32 deletions(-) create mode 100644 docs/_files/commit-status-flow.png create mode 100644 docs/_files/commit-status-github-failure.png create mode 100644 docs/_files/commit-status-github-overview.png create mode 100644 docs/_files/commit-status-github-success.png delete mode 100644 docs/_files/github-commit-status.png delete mode 100644 docs/_files/gitlab-commit-status.png diff --git a/docs/_files/commit-status-flow.png b/docs/_files/commit-status-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..e30fc6fc80028fd8261af44708c3aa79307a0fe5 GIT binary patch literal 39347 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 literal 0 HcmV?d00001 diff --git a/docs/_files/commit-status-github-failure.png b/docs/_files/commit-status-github-failure.png new file mode 100644 index 0000000000000000000000000000000000000000..09e92464ae957ea4cac4b462116c3fc70ecf9909 GIT binary patch literal 18152 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≀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 K=W@|6e*XiNk9-jT literal 0 HcmV?d00001 diff --git a/docs/_files/commit-status-github-success.png b/docs/_files/commit-status-github-success.png new file mode 100644 index 0000000000000000000000000000000000000000..17cf5fd10f177b5619cafb8444c634364ec0fc1c GIT binary patch literal 19124 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

=H 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 E2VB+(W&i*H literal 0 HcmV?d00001 diff --git a/docs/_files/github-commit-status.png b/docs/_files/github-commit-status.png deleted file mode 100644 index 65129f352918bcf9d0c100bd75608a0d7c9f926c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11390 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 N002ovPDHLkV1f{&s3ZUY diff --git a/docs/guides/notifications.md b/docs/guides/notifications.md index e7e2090a..35123025 100644 --- a/docs/guides/notifications.md +++ b/docs/guides/notifications.md @@ -105,47 +105,104 @@ When the verbosity is set to `info`, the controller will alert if: ## Git commit status -The `github` and `gitlab` provider are slightly different to the other chat providers. These providers will -link an event back to its source by writing a commit status event to the repository. For more information about how a -commit status works, refer to the [GitHub](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-status-checks) -or [GitLab](https://docs.gitlab.com/ee/api/commits.html) documentation. - -The first image is an example of how it may look like in GitHub while the one below is an example for GitLab. -![github commit status](../_files/github-commit-status.png) -![gitlab commit status](../_files/gitlab-commit-status.png) - -Currently the provider will only work with Alerts for Kustomization resources as the events have to be linked with a -specific git commit. Any other event that does not contain a commit reference will be ignored by the provider. -Each status will contain some additional information from the event which includes the resource kind, name and reason for the event. -It will be displayed in the format of `{{ .Kind }}/{{ .Name }} - {{ .Reason }}`. - -To get started the git provider require an authentication token to communicate with the API. -Follow the [GitHub](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) -or [Gitlab](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) for a detailed guide how to create a token. -Store the generated token in a Secret with the following data format. +The GitHub, GitLab, Bitbucket, and Azure DevOps providers are slightly different to the other providers. Instead of +a stateless stream of events, the git notification providers will link the event with accompanying git commit which +triggered the event. The linking is done by updating the commit status of a specific commit. + + - [GitHub](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-status-checks) + - [GitLab](https://docs.gitlab.com/ee/api/commits.html) + - [Bitbucket](https://developer.atlassian.com/server/bitbucket/how-tos/updating-build-status-for-commits/) + - [Azure DevOps](https://docs.microsoft.com/en-us/rest/api/azure/devops/git/statuses?view=azure-devops-rest-6.0) + +In GitHub the commit status set by notification-controller will result in a green checkmark or red cross next to the commit hash. +Clicking the icon will show more detailed information about the status. +![commit status GitHub overview](../_files/commit-status-github-overview.png) + +Receiving an event in the form of a commit status rather than a message in a chat conversation has the benefit +that it closes the deployment loop giving quick and visible feedback if a commit has reconciled and if it succeeded. +This means that a deployment will work in a similar manner that people are used to with "traditional" push based CD pipelines. +Additionally the status can be fetched from the git providers API for a specific commit. Allowing for custom automation tools +that can automatically promote, commit to a new directory, after receiving a successful commit status. This can all be +done without requiring any access to the Kubernetes cluster. + +As stated before the provider works by referencing the same git repository as the Kustomization controller does. +When a new commit is pushed to the repository, source-controller will sync the commit, triggering the kustomize-controller +to reconcile the new commit. After this is done the kustomize-controller sends an event to the notification-controller +with the result and the commit hash it reconciled. Then notification-controller can update the correct commit and repository +when receiving the event. +![commit status flow](../_files/commit-status-flow.png) + +!!! hint "Limitations" + The git notification providers require that a commit hash present in the meta data + of the event. There for the the providers will only work with `Kustomization` as an + event source, as it is the only resource which includes this data. + +First follow the [get started guide](../../get-started) if you do not have a Kubernetes cluster with Flux installed in it. +You will need a authentication token to communicate with the API. The authentication method depends on +the git provider used, refer to the [Provider CRD](../../components/notification/provider/#git-commit-status) +for details about how to get the correct token. The guide will use GitHub, but the other providers will work in a very similar manner. +The token will need to have write access to the repository it is going to update the commit status in. +Store the generated token in a Secret with the following data format in the cluster. ```yaml apiVersion: v1 kind: Secret metadata: name: github - namespace: gitops-system + namespace: flux-system data: token: ``` +When sending notification events the kustomization-controller will include the commit hash related to the event. +Note that the commit hash in the event does not come from the git repository the `Kustomization` resource +comes from but rather the kustomization source ref. This mean that commit status notifications will not work +if the manifests comes from a repository which the API token is not allowed to write to. + +Copy the manifest content in the "[kustomize](https://github.com/stefanprodan/podinfo/tree/master/kustomize)" directory +into the directory "staging-cluster/flux-system/podinfo" in your fleet-infra repository. Make sure that you also add the +namepsace podinfo. +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: podinfo +``` + +Then create a Kustomization to deploy podinfo. +```yaml +apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 +kind: Kustomization +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 5m + targetNamespace: podinfo + path: ./staging-cluster/podinfo + prune: true + sourceRef: + kind: GitRepository + name: flux-system + healthChecks: + - apiVersion: apps/v1 + kind: Deployment + name: podinfo + namespace: podinfo + timeout: 1m +``` + Creating a git provider is very similar to creating other types of providers. The only caveat being that the provider address needs to point to the same -git repository as the Kustomization resource refers to. +git repository as the event source originates from. ```yaml apiVersion: notification.toolkit.fluxcd.io/v1beta1 kind: Provider metadata: - name: podinfo + name: flux-system namespace: flux-system spec: type: github - channel: general - address: https://github.com/stefanprodan/podinfo + address: https://github.com//fleet-infra secretRef: name: github --- @@ -156,7 +213,7 @@ metadata: namespace: flux-system spec: providerRef: - name: podinfo + name: flux-system eventSeverity: info eventSources: - kind: Kustomization @@ -164,14 +221,135 @@ spec: namespace: flux-system ``` -The secret referenced in the provider is expected to contain a [personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) -to authenticate with the GitHub API. +By now the fleet-infra repository should have a similar directory structure. +``` +fleet-infra +└── staging-cluster/ + ├── flux-system/ + │ ├── gotk-components.yaml + │ ├── gotk-sync.yaml + │ └── kustomization.yaml + ├── podinfo/ + │ ├── namespace.yaml + │ ├── deployment.yaml + │ ├── hpa.yaml + │ ├── service.yaml + │ └── kustomization.yaml + ├── podinfo-kustomization.yaml + └── podinfo-notification.yaml +``` + +If podinfo is deployed and the health checks pass you should get a successful status in +your forked podinfo repository. + +If everything is setup correctly there should now be a green checkmark next to the lastest commit. +Clicking the checkmark should show a detailed view. + +![commit status github successful](../_files/commit-status-github-success.png) + +Generate error + +A deployment failure can be fored by setting an invalid image tag in the podinfo deployment. ```yaml -apiVersion: v1 -kind: Secret +apiVersion: apps/v1 +kind: Deployment metadata: - name: github - namespace: flux-system -data: - token: + name: podinfo +spec: + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: podinfo + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app: podinfo + spec: + containers: + - name: podinfod + image: ghcr.io/stefanprodan/podinfo:fake + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9898 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + - name: grpc + containerPort: 9999 + protocol: TCP + command: + - ./podinfo + - --port=9898 + - --port-metrics=9797 + - --grpc-port=9999 + - --grpc-service-name=podinfo + - --level=info + - --random-delay=false + - --random-error=false + env: + - name: PODINFO_UI_COLOR + value: "#34577c" + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 2000m + memory: 512Mi + requests: + cpu: 100m + memory: 64Mi ``` + +After the commit has been reconciled it should return a failed commit status. +This is where the health check in the Kustomization comes into play together +with the timeout. The health check is used to asses the health of the Kustomization. +A failed commit status will not be sent until the health check timesout. Setting +a lower timeout will give feedback faster, but may sometimes not allow enough time +for a new application to deploy. + +![commit status github failure](../_files/commit-status-github-failure.png) + +### Status changes + +The provider will continuously receive events as they happen, and multiple events may +be received for the same commit hash. The git providers are configured to only update +the status if the status has changed. This is to avoid spamming the commit status +history with the same status over and over again. + +There is an aspect of state fullness that needs to be considered, compared to the other +notification providers, as the events are stored by the git provider. This means that +the status of a commit can change over time. Initially a deployment may be healthy, resulting +in a successful status. Down the line the application, and the health check, may start failing +due to the amount of traffic it receives or external dependencies no longer being available. +The change in the health check would cause the status to go from successful to failed. +It is important to keep this in mind when building any automation tools that deals with the +status, and consider the fact that receiving a successful status once does not mean it will +always be successful. + From a962c17adb0777bcb29fb9c375b28ad59d45b87b Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Tue, 12 Jan 2021 01:12:03 +0100 Subject: [PATCH 32/54] Add gitlab screenshots Signed-off-by: Philip Laine --- docs/_files/commit-status-gitlab-failure.png | Bin 0 -> 17343 bytes docs/_files/commit-status-gitlab-success.png | Bin 0 -> 17936 bytes docs/guides/notifications.md | 19 ++++++++++++------- 3 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 docs/_files/commit-status-gitlab-failure.png create mode 100644 docs/_files/commit-status-gitlab-success.png diff --git a/docs/_files/commit-status-gitlab-failure.png b/docs/_files/commit-status-gitlab-failure.png new file mode 100644 index 0000000000000000000000000000000000000000..a0ba2f99613373543a4a0816292f5a81a556c126 GIT binary patch literal 17343 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 mde&xf_v14#w~*-hO`7nppSAw#nZWBKkc@ Date: Tue, 12 Jan 2021 15:21:05 +0100 Subject: [PATCH 33/54] Minimize deployment yaml Signed-off-by: Philip Laine --- docs/guides/notifications.md | 66 ------------------------------------ 1 file changed, 66 deletions(-) diff --git a/docs/guides/notifications.md b/docs/guides/notifications.md index 6719ba79..e5d4e768 100644 --- a/docs/guides/notifications.md +++ b/docs/guides/notifications.md @@ -255,78 +255,12 @@ A deployment failure can be forced by setting an invalid image tag in the podinf ```yaml apiVersion: apps/v1 kind: Deployment -metadata: - name: podinfo spec: - minReadySeconds: 3 - revisionHistoryLimit: 5 - progressDeadlineSeconds: 60 - strategy: - rollingUpdate: - maxUnavailable: 0 - type: RollingUpdate - selector: - matchLabels: - app: podinfo template: - metadata: - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "9797" - labels: - app: podinfo spec: containers: - name: podinfod image: ghcr.io/stefanprodan/podinfo:fake - imagePullPolicy: IfNotPresent - ports: - - name: http - containerPort: 9898 - protocol: TCP - - name: http-metrics - containerPort: 9797 - protocol: TCP - - name: grpc - containerPort: 9999 - protocol: TCP - command: - - ./podinfo - - --port=9898 - - --port-metrics=9797 - - --grpc-port=9999 - - --grpc-service-name=podinfo - - --level=info - - --random-delay=false - - --random-error=false - env: - - name: PODINFO_UI_COLOR - value: "#34577c" - livenessProbe: - exec: - command: - - podcli - - check - - http - - localhost:9898/healthz - initialDelaySeconds: 5 - timeoutSeconds: 5 - readinessProbe: - exec: - command: - - podcli - - check - - http - - localhost:9898/readyz - initialDelaySeconds: 5 - timeoutSeconds: 5 - resources: - limits: - cpu: 2000m - memory: 512Mi - requests: - cpu: 100m - memory: 64Mi ``` After the commit has been reconciled it should return a failed commit status. From 505701e1c6d8760caea253da18e579f8b87a1120 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Tue, 12 Jan 2021 22:15:45 +0100 Subject: [PATCH 34/54] Fix cluster path in examples Signed-off-by: Philip Laine --- docs/guides/notifications.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/guides/notifications.md b/docs/guides/notifications.md index e5d4e768..98398540 100644 --- a/docs/guides/notifications.md +++ b/docs/guides/notifications.md @@ -159,7 +159,7 @@ comes from but rather the kustomization source ref. This mean that commit status if the manifests comes from a repository which the API token is not allowed to write to. Copy the manifest content in the "[kustomize](https://github.com/stefanprodan/podinfo/tree/master/kustomize)" directory -into the directory "staging-cluster/flux-system/podinfo" in your fleet-infra repository. Make sure that you also add the +into the directory "./clusters/my-cluster/podinfo" in your fleet-infra repository. Make sure that you also add the namespace podinfo. ```yaml apiVersion: v1 @@ -178,7 +178,7 @@ metadata: spec: interval: 5m targetNamespace: podinfo - path: ./staging-cluster/podinfo + path: ./clusters/my-cluster/podinfo prune: true sourceRef: kind: GitRepository @@ -224,19 +224,20 @@ spec: By now the fleet-infra repository should have a similar directory structure. ``` fleet-infra -└── staging-cluster/ - ├── flux-system/ - │ ├── gotk-components.yaml - │ ├── gotk-sync.yaml - │ └── kustomization.yaml - ├── podinfo/ - │ ├── namespace.yaml - │ ├── deployment.yaml - │ ├── hpa.yaml - │ ├── service.yaml - │ └── kustomization.yaml - ├── podinfo-kustomization.yaml - └── podinfo-notification.yaml +└── clusters/ + └── my-cluster/ + ├── flux-system/ + │ ├── gotk-components.yaml + │ ├── gotk-sync.yaml + │ └── kustomization.yaml + ├── podinfo/ + │ ├── namespace.yaml + │ ├── deployment.yaml + │ ├── hpa.yaml + │ ├── service.yaml + │ └── kustomization.yaml + ├── podinfo-kustomization.yaml + └── podinfo-notification.yaml ``` If podinfo is deployed and the health checks pass you should get a successful status in From f632abd8fa3d72bdf59822e7a5f9f3e45330e41f Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Wed, 13 Jan 2021 11:26:00 +0200 Subject: [PATCH 35/54] Remove deprecated source behaviour Signed-off-by: Stefan Prodan --- docs/guides/helmreleases.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/guides/helmreleases.md b/docs/guides/helmreleases.md index 723daeae..3694e818 100644 --- a/docs/guides/helmreleases.md +++ b/docs/guides/helmreleases.md @@ -21,12 +21,6 @@ To be able to release a Helm chart, the source that contains the chart first to the source-controller, so that the `HelmRelease` can reference to it. -A cluster administrator should register trusted sources by creating -the resources in the `flux-system` namespace. By default, the -source-controller watches for sources only in the `flux-system` -namespace, this way cluster admins can prevent untrusted sources from -being registered by users. - ### Helm repository Helm repositories are the recommended source to retrieve Helm charts From a6a303629a8fe3bc079d18cc335ad6d6ff8d5950 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Tue, 12 Jan 2021 16:42:57 +0100 Subject: [PATCH 36/54] Add doc for authenticating gcr Signed-off-by: Somtochi Onyekwere --- docs/guides/image-update.md | 117 +++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 2 deletions(-) diff --git a/docs/guides/image-update.md b/docs/guides/image-update.md index e0bd53b4..d0e25d8f 100644 --- a/docs/guides/image-update.md +++ b/docs/guides/image-update.md @@ -493,9 +493,122 @@ you can manually create an init job using the following command: $ kubectl create job --from=cronjob/ecr-credentials-sync -n flux-system ecr-credentials-sync-init ``` -## GCP Container Registry +### GCP Container Registry -TODO +#### Using access token [short-lived] + +!!!note "Workload Identity" + Please ensure that you enable workload identity for your cluster, create a GCP service account that has access to the container registry and create an IAM policy binding between the GCP service account and the Kubernetes service account so that the pods created by the cronjob can access GCP APIs and get the token. + Take a look at [this guide](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) + + +The access token for GCR expires hourly. +Considering this limitation, one needs to ensure the credentials are being +refreshed before expiration so that the controller can rely on them for +authentication. + +The solution proposed is to create a cronjob that runs every 45 minutes which would +re-create the `docker-registry` secret using a new token. + +Edit and save the following snippet to a file +`./clusters/my-cluster/gcr-sync.yaml`, commit and push it to git. + +```yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: gcr-credentials-sync + namespace: flux-system +rules: +- apiGroups: [""] + resources: + - secrets + verbs: + - delete + - create +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: gcr-credentials-sync + namespace: flux-system +subjects: +- kind: ServiceAccount + name: gcr-credentials-sync +roleRef: + kind: Role + name: gcr-credentials-sync + apiGroup: "" +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + iam.gke.io/gcp-service-account: @.iam.gserviceaccount.com + name: gcr-credentials-sync + namespace: flux-system +--- +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: gcr-credentials-sync + namespace: flux-system +spec: + suspend: false + schedule: "*/45 * * * *" + failedJobsHistoryLimit: 1 + successfulJobsHistoryLimit: 1 + jobTemplate: + spec: + template: + spec: + serviceAccountName: gcr-credentials-sync + restartPolicy: Never + containers: + - image: google/cloud-sdk + name: create-secret + imagePullPolicy: IfNotPresent + env: + - name: SECRET_NAME + value: # this is the generated Secret name + - name: GCR_REGISTRY + value: # fill in the registry name e.g gcr.io, eu.gcr.io + command: + - /bin/bash + - -ce + - |- + kubectl delete secret --ignore-not-found $SECRET_NAME + kubectl create secret docker-registry $SECRET_NAME \ + --docker-server="$GCR_REGISTRY" \ + --docker-username=oauth2accesstoken \ + --docker-password="$(gcloud auth print-access-token)" +``` + +Since the cronjob will not create a job right away, after applying the manifest, +you can manually create an init job using the following command: + +```console +$ kubectl create job --from=cronjob/gcr-credentials-sync -n flux-system gcr-credentials-sync-init +``` + +#### Using a JSON key [long-lived] + +!!! warning "Less secure option" + From [Google documentation on authenticating container registry](https://cloud.google.com/container-registry/docs/advanced-authentication#json-key) + > A user-managed key-pair that you can use as a credential for a service account. Because the credential is long-lived, it is the least secure option of all the available authentication methods. When possible, use an access token or another available authentication method to reduce the risk of unauthorized access to your artifacts. If you must use a service account key, ensure that you follow best practices for managing credentials. + + +Json keys doesn't expire so we don't need a cronjob, we just need to create the secret and reference it in the ImagePolicy. + +First, create a json key file by following this [documentation](https://cloud.google.com/container-registry/docs/advanced-authentication). Grant the service account the role of `Container Registry Service Agent` so that it can access GCR and download the json file. + +Then create a secret, encrypt it using [Mozilla SOPS](mozilla-sops.md) or [Sealed Secrets](sealed-secrets.md) , commit and push the encypted file to git. +``` + kubectl create secret docker-registry \ + --docker-server= \ # e.g gcr.io + --docker-username=_json_key \ + --docker-password="$(cat )" +``` ### Azure Container Registry From d03280a12fd5c0731799805e876557d19bd3dfa6 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Wed, 13 Jan 2021 11:21:26 +0000 Subject: [PATCH 37/54] Update to controller-runtime 0.7.0 controller-runtime methods now accept `client.Object` and `client.ObjectList` rather than `runtime.Object`. This means the adapter interfaces need to change signature, but happily, little else. Since the list adapter is now distinct to the object adapter, `len()` can go there instead of the command-specific interfaces. Signed-off-by: Michael Bridgen --- cmd/flux/export.go | 5 ++--- cmd/flux/get.go | 5 ++--- cmd/flux/image.go | 14 ++++++------- cmd/flux/object.go | 18 ++++++++++++----- go.mod | 2 +- go.sum | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 75 insertions(+), 19 deletions(-) diff --git a/cmd/flux/export.go b/cmd/flux/export.go index 4a546369..f33534b3 100644 --- a/cmd/flux/export.go +++ b/cmd/flux/export.go @@ -55,8 +55,7 @@ type exportable interface { // exportableList represents a type that has a list of values, each of // which is exportable. type exportableList interface { - adapter - len() int + listAdapter exportItem(i int) interface{} } @@ -79,7 +78,7 @@ func (export exportCommand) run(cmd *cobra.Command, args []string) error { } if exportAll { - err = kubeClient.List(ctx, export.list.asRuntimeObject(), client.InNamespace(namespace)) + err = kubeClient.List(ctx, export.list.asRuntimeList(), client.InNamespace(namespace)) if err != nil { return err } diff --git a/cmd/flux/get.go b/cmd/flux/get.go index f989e689..60b6ed50 100644 --- a/cmd/flux/get.go +++ b/cmd/flux/get.go @@ -45,8 +45,7 @@ func init() { } type summarisable interface { - adapter - len() int + listAdapter summariseItem(i int, includeNamespace bool) []string headers(includeNamespace bool) []string } @@ -87,7 +86,7 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error { if !allNamespaces { listOpts = append(listOpts, client.InNamespace(namespace)) } - err = kubeClient.List(ctx, get.list.asRuntimeObject(), listOpts...) + err = kubeClient.List(ctx, get.list.asRuntimeList(), listOpts...) if err != nil { return err } diff --git a/cmd/flux/image.go b/cmd/flux/image.go index ed307ae2..8e54078b 100644 --- a/cmd/flux/image.go +++ b/cmd/flux/image.go @@ -17,7 +17,7 @@ limitations under the License. package main import ( - "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" @@ -38,7 +38,7 @@ type imageRepositoryAdapter struct { *imagev1.ImageRepository } -func (a imageRepositoryAdapter) asRuntimeObject() runtime.Object { +func (a imageRepositoryAdapter) asRuntimeObject() client.Object { return a.ImageRepository } @@ -48,7 +48,7 @@ type imageRepositoryListAdapter struct { *imagev1.ImageRepositoryList } -func (a imageRepositoryListAdapter) asRuntimeObject() runtime.Object { +func (a imageRepositoryListAdapter) asRuntimeList() client.ObjectList { return a.ImageRepositoryList } @@ -67,7 +67,7 @@ type imagePolicyAdapter struct { *imagev1.ImagePolicy } -func (a imagePolicyAdapter) asRuntimeObject() runtime.Object { +func (a imagePolicyAdapter) asRuntimeObject() client.Object { return a.ImagePolicy } @@ -77,7 +77,7 @@ type imagePolicyListAdapter struct { *imagev1.ImagePolicyList } -func (a imagePolicyListAdapter) asRuntimeObject() runtime.Object { +func (a imagePolicyListAdapter) asRuntimeList() client.ObjectList { return a.ImagePolicyList } @@ -96,7 +96,7 @@ type imageUpdateAutomationAdapter struct { *autov1.ImageUpdateAutomation } -func (a imageUpdateAutomationAdapter) asRuntimeObject() runtime.Object { +func (a imageUpdateAutomationAdapter) asRuntimeObject() client.Object { return a.ImageUpdateAutomation } @@ -106,7 +106,7 @@ type imageUpdateAutomationListAdapter struct { *autov1.ImageUpdateAutomationList } -func (a imageUpdateAutomationListAdapter) asRuntimeObject() runtime.Object { +func (a imageUpdateAutomationListAdapter) asRuntimeList() client.ObjectList { return a.ImageUpdateAutomationList } diff --git a/cmd/flux/object.go b/cmd/flux/object.go index 4b884e35..6bacf890 100644 --- a/cmd/flux/object.go +++ b/cmd/flux/object.go @@ -17,7 +17,7 @@ limitations under the License. package main import ( - "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" ) // Most commands need one or both of the kind (e.g., @@ -35,16 +35,24 @@ type apiType struct { // use values of the wrapper with `client.Client`, which only deals // with types that have been added to the schema. type adapter interface { - asRuntimeObject() runtime.Object + asRuntimeObject() client.Object } -// universalAdapter is an adapter for any runtime.Object. Use this if +// listAdapater is the analogue to adapter, but for lists; the +// controller runtime distinguishes between methods dealing with +// objects and lists. +type listAdapter interface { + asRuntimeList() client.ObjectList + len() int +} + +// universalAdapter is an adapter for any client.Object. Use this if // there are no other methods needed. type universalAdapter struct { - obj runtime.Object + obj client.Object } -func (c universalAdapter) asRuntimeObject() runtime.Object { +func (c universalAdapter) asRuntimeObject() client.Object { return c.obj } diff --git a/go.mod b/go.mod index 3ed9c30a..034dc94a 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( k8s.io/apiextensions-apiserver v0.19.4 k8s.io/apimachinery v0.19.4 k8s.io/client-go v0.19.4 - sigs.k8s.io/controller-runtime v0.6.4 + sigs.k8s.io/controller-runtime v0.7.0 sigs.k8s.io/kustomize/api v0.7.0 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index f913809b..6cba0860 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,7 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L 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= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= @@ -233,6 +234,8 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.2.1 h1:fV3MLmabKIZ383XifUjFSwcoGee0v9qgPp8wy5svibE= github.com/go-logr/logr v0.2.1/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.3.0 h1:q4c+kbcR0d5rSurhBR8dIgieOaYpXtsdTYfx22Cu6rs= +github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-logr/zapr v0.2.0 h1:v6Ji8yBW77pva6NkJKQdHLAJKrIJKRHz0RXwPqCHSR4= github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= @@ -358,6 +361,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-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= @@ -388,6 +393,8 @@ github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1 h1:A8Yhf6EtqTv9RMsU6MQTyrtV1TjWlR6xU9BsZIwuTCM= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -426,6 +433,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -537,6 +546,8 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= +github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -544,6 +555,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= @@ -625,6 +638,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= @@ -685,11 +699,21 @@ go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0H go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -732,6 +756,7 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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 h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/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= @@ -740,6 +765,7 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/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= @@ -837,6 +863,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8= @@ -854,6 +881,8 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -889,6 +918,9 @@ golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -905,6 +937,7 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3 h1:DywqrEscRX7O2phNjkT0L6lhHKGBoMLCNX+XcAe7t6s= golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 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= @@ -913,6 +946,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gomodules.xyz/jsonpatch/v2 v2.1.0 h1:Phva6wqu+xR//Njw6iorylFFgn/z547tw5Ne3HZPQ+k= +gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= @@ -1007,6 +1042,8 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo= gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1014,36 +1051,45 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= +k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs= k8s.io/api v0.19.4 h1:I+1I4cgJYuCDgiLNjKx7SLmIbwgj9w7N7Zr5vSIdwpo= k8s.io/api v0.19.4/go.mod h1:SbtJ2aHCItirzdJ36YslycFNzWADYH3tgOhvBEFtZAk= k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= +k8s.io/apiextensions-apiserver v0.19.2/go.mod h1:EYNjpqIAvNZe+svXVx9j4uBaVhTB4C94HkY3w058qcg= k8s.io/apiextensions-apiserver v0.19.4 h1:D9ak9T012tb3vcGFWYmbQuj9SCC8YM4zhA4XZqsAQC4= k8s.io/apiextensions-apiserver v0.19.4/go.mod h1:B9rpH/nu4JBCtuUp3zTTk8DEjZUupZTBEec7/2zNRYw= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= +k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apimachinery v0.19.4 h1:+ZoddM7nbzrDCp0T3SWnyxqf8cbWPT2fkZImoyvHUG0= k8s.io/apimachinery v0.19.4/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM= +k8s.io/apiserver v0.19.2/go.mod h1:FreAq0bJ2vtZFj9Ago/X0oNGC51GfubKK/ViOKfVAOA= k8s.io/apiserver v0.19.4/go.mod h1:X8WRHCR1UGZDd7HpV0QDc1h/6VbbpAeAGyxSh8yzZXw= k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= +k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= k8s.io/client-go v0.19.4 h1:85D3mDNoLF+xqpyE9Dh/OtrJDyJrSRKkHmDXIbEzer8= k8s.io/client-go v0.19.4/go.mod h1:ZrEy7+wj9PjH5VMBCuu/BDlvtUAku0oVFk4MmnW9mWA= 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.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/code-generator v0.19.2/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= k8s.io/code-generator v0.19.4/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU= +k8s.io/component-base v0.19.2/go.mod h1:g5LrsiTiabMLZ40AR6Hl45f088DevyGY+cCE2agEIVo= +k8s.io/component-base v0.19.4 h1:HobPRToQ8KJ9ubRju6PUAk9I5V1GNMJZ4PyWbiWA0uI= k8s.io/component-base v0.19.4/go.mod h1:ZzuSLlsWhajIDEkKF73j64Gz/5o0AgON08FgRbEPI70= 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= @@ -1068,6 +1114,8 @@ k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20200912215256-4140de9c8800 h1:9ZNvfPvVIEsp/T1ez4GQuzCcCTEQWhovSofhqR73A6g= +k8s.io/utils v0.0.0-20200912215256-4140de9c8800/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= @@ -1084,6 +1132,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQb sigs.k8s.io/controller-runtime v0.6.3/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY= sigs.k8s.io/controller-runtime v0.6.4 h1:4013CKsBs5bEqo+LevzDett+LLxag/FjQWG94nVZ/9g= sigs.k8s.io/controller-runtime v0.6.4/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY= +sigs.k8s.io/controller-runtime v0.7.0 h1:bU20IBBEPccWz5+zXpLnpVsgBYxqclaHu1pVDl/gEt8= +sigs.k8s.io/controller-runtime v0.7.0/go.mod h1:pJ3YBrJiAqMAZKi6UVGuE98ZrroV1p+pIhoHsMm9wdU= sigs.k8s.io/kustomize/api v0.7.0 h1:djxH9k1izeU1BvdP1i23qqKwhmWu2BuKNEKr/Da7Dpw= sigs.k8s.io/kustomize/api v0.7.0/go.mod h1:3TxKEyaxwOIfHmRbQF14hDUSRmVQI0iSn8qDA5zaO/0= sigs.k8s.io/kustomize/kyaml v0.10.3 h1:ARSJUMN/c3k31DYxRfZ+vp/UepUQjg9zCwny7Oj908I= From cafce536bb71c36a58de5c37d4f760651e075d0e Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Wed, 13 Jan 2021 11:38:02 +0000 Subject: [PATCH 38/54] Rename asRuntime* -> asClient* For the avoidance of misdirection. Signed-off-by: Michael Bridgen --- cmd/flux/create.go | 2 +- cmd/flux/delete.go | 4 ++-- cmd/flux/export.go | 4 ++-- cmd/flux/get.go | 2 +- cmd/flux/image.go | 12 ++++++------ cmd/flux/object.go | 6 +++--- cmd/flux/reconcile.go | 8 ++++---- cmd/flux/resume.go | 4 ++-- cmd/flux/status.go | 2 +- cmd/flux/suspend.go | 4 ++-- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cmd/flux/create.go b/cmd/flux/create.go index 10ef64c2..5cdf2f61 100644 --- a/cmd/flux/create.go +++ b/cmd/flux/create.go @@ -76,7 +76,7 @@ func (names apiType) upsert(ctx context.Context, kubeClient client.Client, objec Name: object.GetName(), } - op, err := controllerutil.CreateOrUpdate(ctx, kubeClient, object.asRuntimeObject(), mutate) + op, err := controllerutil.CreateOrUpdate(ctx, kubeClient, object.asClientObject(), mutate) if err != nil { return nsname, err } diff --git a/cmd/flux/delete.go b/cmd/flux/delete.go index ae6e3d31..d03ea43a 100644 --- a/cmd/flux/delete.go +++ b/cmd/flux/delete.go @@ -68,7 +68,7 @@ func (del deleteCommand) run(cmd *cobra.Command, args []string) error { Name: name, } - err = kubeClient.Get(ctx, namespacedName, del.object.asRuntimeObject()) + err = kubeClient.Get(ctx, namespacedName, del.object.asClientObject()) if err != nil { return err } @@ -84,7 +84,7 @@ func (del deleteCommand) run(cmd *cobra.Command, args []string) error { } logger.Actionf("deleting %s %s in %s namespace", del.humanKind, name, namespace) - err = kubeClient.Delete(ctx, del.object.asRuntimeObject()) + err = kubeClient.Delete(ctx, del.object.asClientObject()) if err != nil { return err } diff --git a/cmd/flux/export.go b/cmd/flux/export.go index f33534b3..ec475306 100644 --- a/cmd/flux/export.go +++ b/cmd/flux/export.go @@ -78,7 +78,7 @@ func (export exportCommand) run(cmd *cobra.Command, args []string) error { } if exportAll { - err = kubeClient.List(ctx, export.list.asRuntimeList(), client.InNamespace(namespace)) + err = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(namespace)) if err != nil { return err } @@ -99,7 +99,7 @@ func (export exportCommand) run(cmd *cobra.Command, args []string) error { Namespace: namespace, Name: name, } - err = kubeClient.Get(ctx, namespacedName, export.object.asRuntimeObject()) + err = kubeClient.Get(ctx, namespacedName, export.object.asClientObject()) if err != nil { return err } diff --git a/cmd/flux/get.go b/cmd/flux/get.go index 60b6ed50..580d807b 100644 --- a/cmd/flux/get.go +++ b/cmd/flux/get.go @@ -86,7 +86,7 @@ func (get getCommand) run(cmd *cobra.Command, args []string) error { if !allNamespaces { listOpts = append(listOpts, client.InNamespace(namespace)) } - err = kubeClient.List(ctx, get.list.asRuntimeList(), listOpts...) + err = kubeClient.List(ctx, get.list.asClientList(), listOpts...) if err != nil { return err } diff --git a/cmd/flux/image.go b/cmd/flux/image.go index 8e54078b..4bf6e57b 100644 --- a/cmd/flux/image.go +++ b/cmd/flux/image.go @@ -38,7 +38,7 @@ type imageRepositoryAdapter struct { *imagev1.ImageRepository } -func (a imageRepositoryAdapter) asRuntimeObject() client.Object { +func (a imageRepositoryAdapter) asClientObject() client.Object { return a.ImageRepository } @@ -48,7 +48,7 @@ type imageRepositoryListAdapter struct { *imagev1.ImageRepositoryList } -func (a imageRepositoryListAdapter) asRuntimeList() client.ObjectList { +func (a imageRepositoryListAdapter) asClientList() client.ObjectList { return a.ImageRepositoryList } @@ -67,7 +67,7 @@ type imagePolicyAdapter struct { *imagev1.ImagePolicy } -func (a imagePolicyAdapter) asRuntimeObject() client.Object { +func (a imagePolicyAdapter) asClientObject() client.Object { return a.ImagePolicy } @@ -77,7 +77,7 @@ type imagePolicyListAdapter struct { *imagev1.ImagePolicyList } -func (a imagePolicyListAdapter) asRuntimeList() client.ObjectList { +func (a imagePolicyListAdapter) asClientList() client.ObjectList { return a.ImagePolicyList } @@ -96,7 +96,7 @@ type imageUpdateAutomationAdapter struct { *autov1.ImageUpdateAutomation } -func (a imageUpdateAutomationAdapter) asRuntimeObject() client.Object { +func (a imageUpdateAutomationAdapter) asClientObject() client.Object { return a.ImageUpdateAutomation } @@ -106,7 +106,7 @@ type imageUpdateAutomationListAdapter struct { *autov1.ImageUpdateAutomationList } -func (a imageUpdateAutomationListAdapter) asRuntimeList() client.ObjectList { +func (a imageUpdateAutomationListAdapter) asClientList() client.ObjectList { return a.ImageUpdateAutomationList } diff --git a/cmd/flux/object.go b/cmd/flux/object.go index 6bacf890..39998167 100644 --- a/cmd/flux/object.go +++ b/cmd/flux/object.go @@ -35,14 +35,14 @@ type apiType struct { // use values of the wrapper with `client.Client`, which only deals // with types that have been added to the schema. type adapter interface { - asRuntimeObject() client.Object + asClientObject() client.Object } // listAdapater is the analogue to adapter, but for lists; the // controller runtime distinguishes between methods dealing with // objects and lists. type listAdapter interface { - asRuntimeList() client.ObjectList + asClientList() client.ObjectList len() int } @@ -52,7 +52,7 @@ type universalAdapter struct { obj client.Object } -func (c universalAdapter) asRuntimeObject() client.Object { +func (c universalAdapter) asClientObject() client.Object { return c.obj } diff --git a/cmd/flux/reconcile.go b/cmd/flux/reconcile.go index 3418e789..c348be0d 100644 --- a/cmd/flux/reconcile.go +++ b/cmd/flux/reconcile.go @@ -82,7 +82,7 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error { Name: name, } - err = kubeClient.Get(ctx, namespacedName, reconcile.object.asRuntimeObject()) + err = kubeClient.Get(ctx, namespacedName, reconcile.object.asClientObject()) if err != nil { return err } @@ -115,7 +115,7 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error { func reconciliationHandled(ctx context.Context, kubeClient client.Client, namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionFunc { return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, obj.asRuntimeObject()) + err := kubeClient.Get(ctx, namespacedName, obj.asClientObject()) if err != nil { return false, err } @@ -126,7 +126,7 @@ func reconciliationHandled(ctx context.Context, kubeClient client.Client, func requestReconciliation(ctx context.Context, kubeClient client.Client, namespacedName types.NamespacedName, obj reconcilable) error { return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { - if err := kubeClient.Get(ctx, namespacedName, obj.asRuntimeObject()); err != nil { + if err := kubeClient.Get(ctx, namespacedName, obj.asClientObject()); err != nil { return err } if ann := obj.GetAnnotations(); ann == nil { @@ -137,6 +137,6 @@ func requestReconciliation(ctx context.Context, kubeClient client.Client, ann[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) obj.SetAnnotations(ann) } - return kubeClient.Update(ctx, obj.asRuntimeObject()) + return kubeClient.Update(ctx, obj.asClientObject()) }) } diff --git a/cmd/flux/resume.go b/cmd/flux/resume.go index f1688a7a..35522456 100644 --- a/cmd/flux/resume.go +++ b/cmd/flux/resume.go @@ -68,14 +68,14 @@ func (resume resumeCommand) run(cmd *cobra.Command, args []string) error { Name: name, } - err = kubeClient.Get(ctx, namespacedName, resume.object.asRuntimeObject()) + err = kubeClient.Get(ctx, namespacedName, resume.object.asClientObject()) if err != nil { return err } logger.Actionf("resuming %s %s in %s namespace", resume.humanKind, name, namespace) resume.object.setUnsuspended() - if err := kubeClient.Update(ctx, resume.object.asRuntimeObject()); err != nil { + if err := kubeClient.Update(ctx, resume.object.asClientObject()); err != nil { return err } logger.Successf("%s resumed", resume.humanKind) diff --git a/cmd/flux/status.go b/cmd/flux/status.go index f9dc7615..1e421e3e 100644 --- a/cmd/flux/status.go +++ b/cmd/flux/status.go @@ -42,7 +42,7 @@ type statusable interface { func isReady(ctx context.Context, kubeClient client.Client, namespacedName types.NamespacedName, object statusable) wait.ConditionFunc { return func() (bool, error) { - err := kubeClient.Get(ctx, namespacedName, object.asRuntimeObject()) + err := kubeClient.Get(ctx, namespacedName, object.asClientObject()) if err != nil { return false, err } diff --git a/cmd/flux/suspend.go b/cmd/flux/suspend.go index 5e6a9f34..89a661bf 100644 --- a/cmd/flux/suspend.go +++ b/cmd/flux/suspend.go @@ -65,14 +65,14 @@ func (suspend suspendCommand) run(cmd *cobra.Command, args []string) error { Namespace: namespace, Name: name, } - err = kubeClient.Get(ctx, namespacedName, suspend.object.asRuntimeObject()) + err = kubeClient.Get(ctx, namespacedName, suspend.object.asClientObject()) if err != nil { return err } logger.Actionf("suspending %s %s in %s namespace", suspend.humanKind, name, namespace) suspend.object.setSuspended() - if err := kubeClient.Update(ctx, suspend.object.asRuntimeObject()); err != nil { + if err := kubeClient.Update(ctx, suspend.object.asClientObject()); err != nil { return err } logger.Successf("%s suspended", suspend.humanKind) From 46bcf5da33c9e01c550d3ec7edc60d6dc75732c9 Mon Sep 17 00:00:00 2001 From: Stacey Potter <50154848+staceypotter@users.noreply.github.com> Date: Sun, 10 Jan 2021 14:07:18 -0500 Subject: [PATCH 39/54] Added Jan 25 Meetup to Upcoming Events section Signed-off-by: Stacey Potter <50154848+staceypotter@users.noreply.github.com> --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8e8294e4..a7615f98 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ Depending on what you want to do, some of the following bits might be your first ### Upcoming Events - 11 Jan 2021 - [Helm + GitOps = ⚡️⚡️⚡️ with Scott Rigby](https://www.meetup.com/GitOps-Community/events/275348736/) +- 25 Jan 2021 - [GitOps Core Concepts & How to Teach Your Teams with Leigh Capili](https://www.meetup.com/GitOps-Community/events/275625806/) ### Featured Talks - 14 Dec 2020 - [The Power of GitOps with Flux and Flagger (GitOps Hands-On) with Leigh Capili](https://youtu.be/cB7iXeNLteE) From 31f166cd02557506b69c47278addffaa96ebfc57 Mon Sep 17 00:00:00 2001 From: Stacey Potter <50154848+staceypotter@users.noreply.github.com> Date: Wed, 13 Jan 2021 09:53:35 -0500 Subject: [PATCH 40/54] Moved 11 Jan talk from Upcoming to Featured MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated and moved this from Upcoming Events: - 11 Jan 2021 - [Helm + GitOps = ⚡️⚡️⚡️ with Scott Rigby](https://www.meetup.com/GitOps-Community/events/275348736/) To this in Featured Talks: - 11 Jan 2021 - [Helm + GitOps = ⚡️⚡️⚡️ with Scott Rigby](https://youtu.be/YG8jMFrYQvM) Signed-off-by: Stacey Potter <50154848+staceypotter@users.noreply.github.com> --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 45c616a8..065ec915 100644 --- a/docs/index.md +++ b/docs/index.md @@ -85,10 +85,10 @@ Depending on what you want to do, some of the following bits might be your first - Check out [how to contribute](contributing/index.md) to the project ### Upcoming Events -- 11 Jan 2021 - [Helm + GitOps = ⚡️⚡️⚡️ with Scott Rigby](https://www.meetup.com/GitOps-Community/events/275348736/) - 25 Jan 2021 - [GitOps Core Concepts & How to Teach Your Teams with Leigh Capili](https://www.meetup.com/GitOps-Community/events/275625806/) ### Featured Talks +- 11 Jan 2021 - [Helm + GitOps = ⚡️⚡️⚡️ with Scott Rigby](https://youtu.be/YG8jMFrYQvM) - 14 Dec 2020 - [The Power of GitOps with Flux and Flagger (GitOps Hands-On) with Leigh Capili](https://youtu.be/cB7iXeNLteE) - 30 Nov 2020 - [The Power of GItOps with Flux 2 - Part 3 with Leigh Capili](https://youtu.be/N_K5g7o9JKg) - 24 Nov 2020 - [Flux CD v2 with GitOps Toolkit - Kubernetes Deployment and Sync Mechanism](https://youtu.be/R6OeIgb7lUI) From d236a9af5747e4e783138df8edc0595789cd6198 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 13 Jan 2021 23:44:24 +0100 Subject: [PATCH 41/54] Add git implementation to generate sync options Signed-off-by: Philip Laine --- cmd/flux/bootstrap.go | 15 ++++++++------- cmd/flux/bootstrap_github.go | 2 +- cmd/flux/bootstrap_gitlab.go | 4 ++-- docs/cmd/flux_bootstrap_github.md | 2 +- docs/cmd/flux_bootstrap_gitlab.md | 4 ++-- pkg/manifestgen/sync/options.go | 30 ++++++++++++++++-------------- pkg/manifestgen/sync/sync.go | 1 + 7 files changed, 31 insertions(+), 27 deletions(-) diff --git a/cmd/flux/bootstrap.go b/cmd/flux/bootstrap.go index 0bf38de6..9bccfdd4 100644 --- a/cmd/flux/bootstrap.go +++ b/cmd/flux/bootstrap.go @@ -164,13 +164,14 @@ func applyInstallManifests(ctx context.Context, manifestPath string, components func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir string, interval time.Duration) (string, error) { opts := sync.Options{ - Name: name, - Namespace: namespace, - URL: url, - Branch: branch, - Interval: interval, - TargetPath: targetPath, - ManifestFile: sync.MakeDefaultOptions().ManifestFile, + Name: name, + Namespace: namespace, + URL: url, + Branch: branch, + Interval: interval, + TargetPath: targetPath, + ManifestFile: sync.MakeDefaultOptions().ManifestFile, + GitImplementation: sync.MakeDefaultOptions().GitImplementation, } manifest, err := sync.Generate(opts) diff --git a/cmd/flux/bootstrap_github.go b/cmd/flux/bootstrap_github.go index a0e808d9..336423de 100644 --- a/cmd/flux/bootstrap_github.go +++ b/cmd/flux/bootstrap_github.go @@ -57,7 +57,7 @@ the bootstrap command will perform an upgrade if needed.`, flux bootstrap github --owner= --repository= --path=dev-cluster # Run bootstrap for a public repository on a personal account - flux bootstrap github --owner= --repository= --private=false --personal=true + flux bootstrap github --owner= --repository= --private=false --personal=true # Run bootstrap for a private repo hosted on GitHub Enterprise using SSH auth flux bootstrap github --owner= --repository= --hostname= --ssh-hostname= diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go index c2b0b609..dbbc5793 100644 --- a/cmd/flux/bootstrap_gitlab.go +++ b/cmd/flux/bootstrap_gitlab.go @@ -48,7 +48,7 @@ the bootstrap command will perform an upgrade if needed.`, Example: ` # Create a GitLab API token and export it as an env var export GITLAB_TOKEN= - # Run bootstrap for a private repo using HTTPS token authentication + # Run bootstrap for a private repo using HTTPS token authentication flux bootstrap gitlab --owner= --repository= --token-auth # Run bootstrap for a private repo using SSH authentication @@ -60,7 +60,7 @@ the bootstrap command will perform an upgrade if needed.`, # Run bootstrap for a public repository on a personal account flux bootstrap gitlab --owner= --repository= --private=false --personal --token-auth - # Run bootstrap for a private repo hosted on a GitLab server + # Run bootstrap for a private repo hosted on a GitLab server flux bootstrap gitlab --owner= --repository= --hostname= --token-auth # Run bootstrap for a an existing repository with a branch named main diff --git a/docs/cmd/flux_bootstrap_github.md b/docs/cmd/flux_bootstrap_github.md index 2836d346..784579e3 100644 --- a/docs/cmd/flux_bootstrap_github.md +++ b/docs/cmd/flux_bootstrap_github.md @@ -30,7 +30,7 @@ flux bootstrap github [flags] flux bootstrap github --owner= --repository= --path=dev-cluster # Run bootstrap for a public repository on a personal account - flux bootstrap github --owner= --repository= --private=false --personal=true + flux bootstrap github --owner= --repository= --private=false --personal=true # Run bootstrap for a private repo hosted on GitHub Enterprise using SSH auth flux bootstrap github --owner= --repository= --hostname= --ssh-hostname= diff --git a/docs/cmd/flux_bootstrap_gitlab.md b/docs/cmd/flux_bootstrap_gitlab.md index 399c8370..3df2f26e 100644 --- a/docs/cmd/flux_bootstrap_gitlab.md +++ b/docs/cmd/flux_bootstrap_gitlab.md @@ -20,7 +20,7 @@ flux bootstrap gitlab [flags] # Create a GitLab API token and export it as an env var export GITLAB_TOKEN= - # Run bootstrap for a private repo using HTTPS token authentication + # Run bootstrap for a private repo using HTTPS token authentication flux bootstrap gitlab --owner= --repository= --token-auth # Run bootstrap for a private repo using SSH authentication @@ -32,7 +32,7 @@ flux bootstrap gitlab [flags] # Run bootstrap for a public repository on a personal account flux bootstrap gitlab --owner= --repository= --private=false --personal --token-auth - # Run bootstrap for a private repo hosted on a GitLab server + # Run bootstrap for a private repo hosted on a GitLab server flux bootstrap gitlab --owner= --repository= --hostname= --token-auth # Run bootstrap for a an existing repository with a branch named main diff --git a/pkg/manifestgen/sync/options.go b/pkg/manifestgen/sync/options.go index 411612af..93b37acc 100644 --- a/pkg/manifestgen/sync/options.go +++ b/pkg/manifestgen/sync/options.go @@ -19,23 +19,25 @@ package sync import "time" type Options struct { - Interval time.Duration - URL string - Name string - Namespace string - Branch string - TargetPath string - ManifestFile string + Interval time.Duration + URL string + Name string + Namespace string + Branch string + TargetPath string + ManifestFile string + GitImplementation string } func MakeDefaultOptions() Options { return Options{ - Interval: 1 * time.Minute, - URL: "", - Name: "flux-system", - Namespace: "flux-system", - Branch: "main", - ManifestFile: "gotk-sync.yaml", - TargetPath: "", + Interval: 1 * time.Minute, + URL: "", + Name: "flux-system", + Namespace: "flux-system", + Branch: "main", + ManifestFile: "gotk-sync.yaml", + TargetPath: "", + GitImplementation: "go-git", } } diff --git a/pkg/manifestgen/sync/sync.go b/pkg/manifestgen/sync/sync.go index 840d8498..d112f6dd 100644 --- a/pkg/manifestgen/sync/sync.go +++ b/pkg/manifestgen/sync/sync.go @@ -55,6 +55,7 @@ func Generate(options Options) (*manifestgen.Manifest, error) { SecretRef: &corev1.LocalObjectReference{ Name: options.Name, }, + GitImplementation: options.GitImplementation, }, } From 3b249dfe69a125fcc4cc2b1e3e16afe90660cbe6 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 14 Jan 2021 00:21:57 +0100 Subject: [PATCH 42/54] Change default to use const Signed-off-by: Philip Laine --- pkg/manifestgen/sync/options.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/manifestgen/sync/options.go b/pkg/manifestgen/sync/options.go index 93b37acc..4fb5955a 100644 --- a/pkg/manifestgen/sync/options.go +++ b/pkg/manifestgen/sync/options.go @@ -16,7 +16,11 @@ limitations under the License. package sync -import "time" +import ( + "time" + + sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" +) type Options struct { Interval time.Duration @@ -38,6 +42,6 @@ func MakeDefaultOptions() Options { Branch: "main", ManifestFile: "gotk-sync.yaml", TargetPath: "", - GitImplementation: "go-git", + GitImplementation: sourcev1.GoGitImplementation, } } From 8214bb8e337da1b8d84b4ba87b3a390068f1fe59 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Thu, 14 Jan 2021 13:38:38 +0200 Subject: [PATCH 43/54] Allow egress traffic for controller pods Signed-off-by: Stefan Prodan --- manifests/policies/deny-ingress.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/manifests/policies/deny-ingress.yaml b/manifests/policies/deny-ingress.yaml index 5577032b..a7813719 100644 --- a/manifests/policies/deny-ingress.yaml +++ b/manifests/policies/deny-ingress.yaml @@ -5,7 +5,10 @@ metadata: spec: policyTypes: - Ingress + - Egress ingress: - from: - podSelector: {} + egress: + - {} podSelector: {} From 406601eead9b789dc01f1f177bfcdae38f81a2bb Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Thu, 14 Jan 2021 20:09:55 +0100 Subject: [PATCH 44/54] Update pkg/git to v0.2.2 Signed-off-by: Somtochi Onyekwere --- cmd/flux/bootstrap_gitlab.go | 16 ++++++---------- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go index dbbc5793..5767a0e0 100644 --- a/cmd/flux/bootstrap_gitlab.go +++ b/cmd/flux/bootstrap_gitlab.go @@ -129,16 +129,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("cluster already bootstrapped to %v path", usedPath) } - provider := &git.GitLabProvider{ - IsPrivate: glPrivate, - IsPersonal: glPersonal, - } - owner, err := provider.GetRepositoryOwner(ctx, glToken, glOwner) - if err != nil { - return err - } - - repository, err := git.NewRepository(glRepository, owner, glHostname, glToken, "flux", glOwner+"@users.noreply.gitlab.com") + repository, err := git.NewRepository(glRepository, glOwner, glHostname, glToken, "flux", glOwner+"@users.noreply.gitlab.com") if err != nil { return err } @@ -153,6 +144,11 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { } defer os.RemoveAll(tmpDir) + provider := &git.GitLabProvider{ + IsPrivate: glPrivate, + IsPersonal: glPersonal, + } + // create GitLab project if doesn't exists logger.Actionf("connecting to %s", glHostname) changed, err := provider.CreateRepository(ctx, repository) diff --git a/go.mod b/go.mod index 034dc94a..2d3d7fe8 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/fluxcd/kustomize-controller/api v0.5.3 github.com/fluxcd/notification-controller/api v0.5.0 github.com/fluxcd/pkg/apis/meta v0.5.0 - github.com/fluxcd/pkg/git v0.2.0 + github.com/fluxcd/pkg/git v0.2.1 github.com/fluxcd/pkg/runtime v0.4.0 github.com/fluxcd/pkg/ssh v0.0.5 github.com/fluxcd/pkg/untar v0.0.5 diff --git a/go.sum b/go.sum index 6cba0860..4427f66e 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,8 @@ github.com/fluxcd/notification-controller/api v0.5.0 h1:xKKFnPVsYl2+GEjgKz5a5Mq6 github.com/fluxcd/notification-controller/api v0.5.0/go.mod h1:yLd+nrCJUThSkt4U+LLv8TRxqZdR11+gE1S2/bhgqmE= github.com/fluxcd/pkg/apis/meta v0.5.0 h1:FaU++mQY0g4sVVl+hG+vk0CXBLbb4EVfRuzs3IjLXvo= github.com/fluxcd/pkg/apis/meta v0.5.0/go.mod h1:aEUuZIawboAAFLlYz/juVJ7KNmlWbBtJFYkOWWmGUR4= -github.com/fluxcd/pkg/git v0.2.0 h1:EFFa8ud+9OgEQ7IhyG1pV9+jsHycBtDEHal5odjuvMM= -github.com/fluxcd/pkg/git v0.2.0/go.mod h1:8v0QVumu1ugMG3nJL0KMYPZgmLjzesJHA2sOtXAHLPA= +github.com/fluxcd/pkg/git v0.2.1 h1:7as7kMjxTFpM4dpSmwQqM/fYXDlQhZdbNnuytI6O57k= +github.com/fluxcd/pkg/git v0.2.1/go.mod h1:8v0QVumu1ugMG3nJL0KMYPZgmLjzesJHA2sOtXAHLPA= github.com/fluxcd/pkg/runtime v0.4.0 h1:d/1okReK7ZyrQ2k/GKY1BEiMZNHu1rWKUxlHx2O45EY= github.com/fluxcd/pkg/runtime v0.4.0/go.mod h1:0Rbkgh3qj8Dl4uitccLc13hZyet1vvNJCAFAVUwNZDM= github.com/fluxcd/pkg/ssh v0.0.5 h1:rnbFZ7voy2JBlUfMbfyqArX2FYaLNpDhccGFC3qW83A= From fe5f1817064fce1ca2a84da4f27de2dc658d4235 Mon Sep 17 00:00:00 2001 From: Simon Zengerling Date: Wed, 6 Jan 2021 07:12:57 +0100 Subject: [PATCH 45/54] fix(image-update.md): typo maker => marker Signed-off-by: Simon Zengerling --- docs/guides/image-update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/image-update.md b/docs/guides/image-update.md index d0e25d8f..2be6bae8 100644 --- a/docs/guides/image-update.md +++ b/docs/guides/image-update.md @@ -217,7 +217,7 @@ podinfo True Latest image tag for 'ghcr.io/stefanprodan/podinfo' resolved to: 5 ## Configure image updates -Edit the `podinfo-deploy.yaml` and add a maker to tell Flux which policy to use when updating the container image: +Edit the `podinfo-deploy.yaml` and add a marker to tell Flux which policy to use when updating the container image: ```yaml spec: From f5117329e4e57deea16882ac03c2d2041f7809e2 Mon Sep 17 00:00:00 2001 From: fluxcdbot Date: Fri, 15 Jan 2021 09:49:23 +0000 Subject: [PATCH 46/54] Update toolkit components --- go.mod | 14 +++--- go.sum | 49 ++++++------------- .../bases/helm-controller/kustomization.yaml | 4 +- .../kustomization.yaml | 4 +- .../kustomization.yaml | 4 +- .../kustomize-controller/kustomization.yaml | 4 +- .../kustomization.yaml | 4 +- .../source-controller/kustomization.yaml | 4 +- 8 files changed, 33 insertions(+), 54 deletions(-) diff --git a/go.mod b/go.mod index 2d3d7fe8..47aa4cdb 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,17 @@ go 1.15 require ( github.com/blang/semver/v4 v4.0.0 github.com/cyphar/filepath-securejoin v0.2.2 - github.com/fluxcd/helm-controller/api v0.4.4 - github.com/fluxcd/image-automation-controller/api v0.2.0 - github.com/fluxcd/image-reflector-controller/api v0.1.0 - github.com/fluxcd/kustomize-controller/api v0.5.3 - github.com/fluxcd/notification-controller/api v0.5.0 + github.com/fluxcd/helm-controller/api v0.5.1 + github.com/fluxcd/image-automation-controller/api v0.3.0 + github.com/fluxcd/image-reflector-controller/api v0.2.0 + github.com/fluxcd/kustomize-controller/api v0.6.1 + github.com/fluxcd/notification-controller/api v0.6.1 github.com/fluxcd/pkg/apis/meta v0.5.0 github.com/fluxcd/pkg/git v0.2.1 - github.com/fluxcd/pkg/runtime v0.4.0 + github.com/fluxcd/pkg/runtime v0.6.2 github.com/fluxcd/pkg/ssh v0.0.5 github.com/fluxcd/pkg/untar v0.0.5 - github.com/fluxcd/source-controller/api v0.5.6 + github.com/fluxcd/source-controller/api v0.6.1 github.com/google/go-containerregistry v0.2.0 github.com/manifoldco/promptui v0.7.0 github.com/olekukonko/tablewriter v0.0.4 diff --git a/go.sum b/go.sum index 4427f66e..ea01f171 100644 --- a/go.sum +++ b/go.sum @@ -176,28 +176,28 @@ github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fluxcd/helm-controller/api v0.4.4 h1:WYf7KokS3ALeE1F2SrviMHtEBGfznP7DkxXwo5pP7f8= -github.com/fluxcd/helm-controller/api v0.4.4/go.mod h1:H3fHkKJWcxPz38L1kxBX/MGm5v9XKzeoKZWNM7+dW2o= -github.com/fluxcd/image-automation-controller/api v0.2.0 h1:1diY89HhpbseqENkd9DLgqsDnkTGZmVDjX5mucNOMr8= -github.com/fluxcd/image-automation-controller/api v0.2.0/go.mod h1:DHjFNvA+kJlSm7cbTaG+Z5smVjMjLw7xzlJc9brP0zY= -github.com/fluxcd/image-reflector-controller/api v0.1.0 h1:wlqwCy4sZMbbdrgSY9Fd0mfy55kk7dS4Z+icrDlkmmg= -github.com/fluxcd/image-reflector-controller/api v0.1.0/go.mod h1:u7vnULekPHXAZgJ35lqCjV2MaJVN0xbD+qt9X9TVCMs= -github.com/fluxcd/kustomize-controller/api v0.5.3 h1:zZiWFBQkNytLffOOJJttGFQk7BIUHpT2yoOCG4nyqII= -github.com/fluxcd/kustomize-controller/api v0.5.3/go.mod h1:8Z52j63kRf+NjtVmiJFvI8xLje3ncFTs/uMxcrEJPIA= -github.com/fluxcd/notification-controller/api v0.5.0 h1:xKKFnPVsYl2+GEjgKz5a5Mq6vmy+H2q9d2lJ2jmWJZs= -github.com/fluxcd/notification-controller/api v0.5.0/go.mod h1:yLd+nrCJUThSkt4U+LLv8TRxqZdR11+gE1S2/bhgqmE= +github.com/fluxcd/helm-controller/api v0.5.1 h1:GpDZSBClBwUivDjQDIsYGALwiBUcTe5IU7HRrbBfzR0= +github.com/fluxcd/helm-controller/api v0.5.1/go.mod h1:NGTkoX7WmNzesSZImqlO7DMEJF3s7ZxIyRylFrMmWLE= +github.com/fluxcd/image-automation-controller/api v0.3.0 h1:1cwNjY4t7gy3k+dYf47xokePx7Q5L5Jt05EL/G6Btbo= +github.com/fluxcd/image-automation-controller/api v0.3.0/go.mod h1:dn/HSWvYMEMv1ILOasXtbM2Y1vTgrv2Edcw69rL7QpQ= +github.com/fluxcd/image-reflector-controller/api v0.2.0 h1:/qAamO2y1Bq4rn/JedB33V9kOkSeXIYxwpnEHBz0HeE= +github.com/fluxcd/image-reflector-controller/api v0.2.0/go.mod h1:2KC4Zijp+iIbkID/KT+hhH4iSSQP3Hrzh0t971tjWjk= +github.com/fluxcd/kustomize-controller/api v0.6.1 h1:NUe+Aa3w6z8PR5zyyOSjAN3RTGNLwb5jGAm+u6aJuWk= +github.com/fluxcd/kustomize-controller/api v0.6.1/go.mod h1:g9cE+lrH5xluslMvx9LqxY/rYg7c2XN/lOlsxDBnNdM= +github.com/fluxcd/notification-controller/api v0.6.1 h1:l5mManb+Y8aT9uO9Y17Ifoo455zLyAbeZz4ltRUKK78= +github.com/fluxcd/notification-controller/api v0.6.1/go.mod h1:9BiC68i6eeQTERhrMo0dni51uKSlt/eMiRcMArcZ++Y= github.com/fluxcd/pkg/apis/meta v0.5.0 h1:FaU++mQY0g4sVVl+hG+vk0CXBLbb4EVfRuzs3IjLXvo= github.com/fluxcd/pkg/apis/meta v0.5.0/go.mod h1:aEUuZIawboAAFLlYz/juVJ7KNmlWbBtJFYkOWWmGUR4= github.com/fluxcd/pkg/git v0.2.1 h1:7as7kMjxTFpM4dpSmwQqM/fYXDlQhZdbNnuytI6O57k= github.com/fluxcd/pkg/git v0.2.1/go.mod h1:8v0QVumu1ugMG3nJL0KMYPZgmLjzesJHA2sOtXAHLPA= -github.com/fluxcd/pkg/runtime v0.4.0 h1:d/1okReK7ZyrQ2k/GKY1BEiMZNHu1rWKUxlHx2O45EY= -github.com/fluxcd/pkg/runtime v0.4.0/go.mod h1:0Rbkgh3qj8Dl4uitccLc13hZyet1vvNJCAFAVUwNZDM= +github.com/fluxcd/pkg/runtime v0.6.2 h1:sWnSv6AhMY30fexRQ37lv2Q9Rvdu05zbiqMSldw+MjQ= +github.com/fluxcd/pkg/runtime v0.6.2/go.mod h1:RuqYOYCvBJwo4rg83d28WOt2vfSaemuZCVpUagAjWQc= 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/source-controller/api v0.5.6 h1:Nj7WebOP8nTTA/yDPwszyjzW5oYI5tVhS/8XWOT+2wk= -github.com/fluxcd/source-controller/api v0.5.6/go.mod h1:/mpW0EM2dUnRey6rffqsSmgNkSAYm+zq9i0GfmTO7I0= +github.com/fluxcd/source-controller/api v0.6.1 h1:sKrq2bS6rANk2BgMNOmS+shBNkzbtIl1/v3BXkVVWag= +github.com/fluxcd/source-controller/api v0.6.1/go.mod h1:5eEXvQGVAuxJmCm/ooewsNTQR7Y9L75/cRP1WF74j5s= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= 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= @@ -232,11 +232,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.2.1 h1:fV3MLmabKIZ383XifUjFSwcoGee0v9qgPp8wy5svibE= -github.com/go-logr/logr v0.2.1/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.3.0 h1:q4c+kbcR0d5rSurhBR8dIgieOaYpXtsdTYfx22Cu6rs= github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-logr/zapr v0.2.0 h1:v6Ji8yBW77pva6NkJKQdHLAJKrIJKRHz0RXwPqCHSR4= github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= @@ -390,7 +387,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1 h1:A8Yhf6EtqTv9RMsU6MQTyrtV1TjWlR6xU9BsZIwuTCM= @@ -589,7 +585,6 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= @@ -944,8 +939,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= -gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gomodules.xyz/jsonpatch/v2 v2.1.0 h1:Phva6wqu+xR//Njw6iorylFFgn/z547tw5Ne3HZPQ+k= gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= @@ -1054,39 +1047,30 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= -k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= -k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs= k8s.io/api v0.19.4 h1:I+1I4cgJYuCDgiLNjKx7SLmIbwgj9w7N7Zr5vSIdwpo= k8s.io/api v0.19.4/go.mod h1:SbtJ2aHCItirzdJ36YslycFNzWADYH3tgOhvBEFtZAk= -k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= k8s.io/apiextensions-apiserver v0.19.2/go.mod h1:EYNjpqIAvNZe+svXVx9j4uBaVhTB4C94HkY3w058qcg= k8s.io/apiextensions-apiserver v0.19.4 h1:D9ak9T012tb3vcGFWYmbQuj9SCC8YM4zhA4XZqsAQC4= k8s.io/apiextensions-apiserver v0.19.4/go.mod h1:B9rpH/nu4JBCtuUp3zTTk8DEjZUupZTBEec7/2zNRYw= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= -k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apimachinery v0.19.4 h1:+ZoddM7nbzrDCp0T3SWnyxqf8cbWPT2fkZImoyvHUG0= k8s.io/apimachinery v0.19.4/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM= k8s.io/apiserver v0.19.2/go.mod h1:FreAq0bJ2vtZFj9Ago/X0oNGC51GfubKK/ViOKfVAOA= k8s.io/apiserver v0.19.4/go.mod h1:X8WRHCR1UGZDd7HpV0QDc1h/6VbbpAeAGyxSh8yzZXw= k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= -k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= k8s.io/client-go v0.19.4 h1:85D3mDNoLF+xqpyE9Dh/OtrJDyJrSRKkHmDXIbEzer8= k8s.io/client-go v0.19.4/go.mod h1:ZrEy7+wj9PjH5VMBCuu/BDlvtUAku0oVFk4MmnW9mWA= 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.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/code-generator v0.19.2/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= k8s.io/code-generator v0.19.4/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= -k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU= k8s.io/component-base v0.19.2/go.mod h1:g5LrsiTiabMLZ40AR6Hl45f088DevyGY+cCE2agEIVo= k8s.io/component-base v0.19.4 h1:HobPRToQ8KJ9ubRju6PUAk9I5V1GNMJZ4PyWbiWA0uI= @@ -1094,7 +1078,6 @@ k8s.io/component-base v0.19.4/go.mod h1:ZzuSLlsWhajIDEkKF73j64Gz/5o0AgON08FgRbEP 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= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -1111,7 +1094,6 @@ k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H k8s.io/legacy-cloud-providers v0.18.8/go.mod h1:tgp4xYf6lvjrWnjQwTOPvWQE9IVqSBGPF4on0IyICQE= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20200912215256-4140de9c8800 h1:9ZNvfPvVIEsp/T1ez4GQuzCcCTEQWhovSofhqR73A6g= @@ -1129,9 +1111,6 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 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.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= -sigs.k8s.io/controller-runtime v0.6.3/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY= -sigs.k8s.io/controller-runtime v0.6.4 h1:4013CKsBs5bEqo+LevzDett+LLxag/FjQWG94nVZ/9g= -sigs.k8s.io/controller-runtime v0.6.4/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY= sigs.k8s.io/controller-runtime v0.7.0 h1:bU20IBBEPccWz5+zXpLnpVsgBYxqclaHu1pVDl/gEt8= sigs.k8s.io/controller-runtime v0.7.0/go.mod h1:pJ3YBrJiAqMAZKi6UVGuE98ZrroV1p+pIhoHsMm9wdU= sigs.k8s.io/kustomize/api v0.7.0 h1:djxH9k1izeU1BvdP1i23qqKwhmWu2BuKNEKr/Da7Dpw= diff --git a/manifests/bases/helm-controller/kustomization.yaml b/manifests/bases/helm-controller/kustomization.yaml index 7ba5d108..385aa05c 100644 --- a/manifests/bases/helm-controller/kustomization.yaml +++ b/manifests/bases/helm-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/helm-controller/archive/v0.4.4.zip//helm-controller-0.4.4/config/crd -- https://github.com/fluxcd/helm-controller/archive/v0.4.4.zip//helm-controller-0.4.4/config/manager +- https://github.com/fluxcd/helm-controller/archive/v0.5.1.zip//helm-controller-0.5.1/config/crd +- https://github.com/fluxcd/helm-controller/archive/v0.5.1.zip//helm-controller-0.5.1/config/manager patchesJson6902: - target: group: apps diff --git a/manifests/bases/image-automation-controller/kustomization.yaml b/manifests/bases/image-automation-controller/kustomization.yaml index a535827c..33589536 100644 --- a/manifests/bases/image-automation-controller/kustomization.yaml +++ b/manifests/bases/image-automation-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/image-automation-controller/archive/v0.2.0.zip//image-automation-controller-0.2.0/config/crd -- https://github.com/fluxcd/image-automation-controller/archive/v0.2.0.zip//image-automation-controller-0.2.0/config/manager +- https://github.com/fluxcd/image-automation-controller/archive/v0.3.0.zip//image-automation-controller-0.3.0/config/crd +- https://github.com/fluxcd/image-automation-controller/archive/v0.3.0.zip//image-automation-controller-0.3.0/config/manager patchesJson6902: - target: group: apps diff --git a/manifests/bases/image-reflector-controller/kustomization.yaml b/manifests/bases/image-reflector-controller/kustomization.yaml index 79486db4..9412a090 100644 --- a/manifests/bases/image-reflector-controller/kustomization.yaml +++ b/manifests/bases/image-reflector-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/image-reflector-controller/archive/v0.1.0.zip//image-reflector-controller-0.1.0/config/crd -- https://github.com/fluxcd/image-reflector-controller/archive/v0.1.0.zip//image-reflector-controller-0.1.0/config/manager +- https://github.com/fluxcd/image-reflector-controller/archive/v0.2.0.zip//image-reflector-controller-0.2.0/config/crd +- https://github.com/fluxcd/image-reflector-controller/archive/v0.2.0.zip//image-reflector-controller-0.2.0/config/manager patchesJson6902: - target: group: apps diff --git a/manifests/bases/kustomize-controller/kustomization.yaml b/manifests/bases/kustomize-controller/kustomization.yaml index 0342c74b..66e103bc 100644 --- a/manifests/bases/kustomize-controller/kustomization.yaml +++ b/manifests/bases/kustomize-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/kustomize-controller/archive/v0.5.3.zip//kustomize-controller-0.5.3/config/crd -- https://github.com/fluxcd/kustomize-controller/archive/v0.5.3.zip//kustomize-controller-0.5.3/config/manager +- https://github.com/fluxcd/kustomize-controller/archive/v0.6.1.zip//kustomize-controller-0.6.1/config/crd +- https://github.com/fluxcd/kustomize-controller/archive/v0.6.1.zip//kustomize-controller-0.6.1/config/manager patchesJson6902: - target: group: apps diff --git a/manifests/bases/notification-controller/kustomization.yaml b/manifests/bases/notification-controller/kustomization.yaml index a39280fb..2a7af04d 100644 --- a/manifests/bases/notification-controller/kustomization.yaml +++ b/manifests/bases/notification-controller/kustomization.yaml @@ -1,5 +1,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/notification-controller/archive/v0.5.0.zip//notification-controller-0.5.0/config/crd -- https://github.com/fluxcd/notification-controller/archive/v0.5.0.zip//notification-controller-0.5.0/config/manager +- https://github.com/fluxcd/notification-controller/archive/v0.6.1.zip//notification-controller-0.6.1/config/crd +- https://github.com/fluxcd/notification-controller/archive/v0.6.1.zip//notification-controller-0.6.1/config/manager diff --git a/manifests/bases/source-controller/kustomization.yaml b/manifests/bases/source-controller/kustomization.yaml index 30a1a780..ae52f8fd 100644 --- a/manifests/bases/source-controller/kustomization.yaml +++ b/manifests/bases/source-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/archive/v0.5.6.zip//source-controller-0.5.6/config/crd -- https://github.com/fluxcd/source-controller/archive/v0.5.6.zip//source-controller-0.5.6/config/manager +- https://github.com/fluxcd/source-controller/archive/v0.6.1.zip//source-controller-0.6.1/config/crd +- https://github.com/fluxcd/source-controller/archive/v0.6.1.zip//source-controller-0.6.1/config/manager patchesJson6902: - target: group: apps From 5e1c93a1673a9122122e6fc0491bf78295cdad4e Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 15 Jan 2021 12:03:47 +0200 Subject: [PATCH 47/54] Add e2e tests for image repository and policy Signed-off-by: Stefan Prodan --- .github/workflows/e2e.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 97af7e78..e7d2e19a 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -146,6 +146,20 @@ jobs: --chart=podinfo \ --chart-version="5.0.x" \ --service-account=dev-team + - name: flux create image repository + run: | + ./bin/flux create image repository podinfo \ + --image=ghcr.io/stefanprodan/podinfo \ + --interval=1m + - name: flux create image policy + run: | + ./bin/flux create image policy podinfo \ + --image-ref=podinfo \ + --interval=1m \ + --semver=5.0.x + - name: flux get image policy + run: | + ./bin/flux get image policy podinfo | grep '5.0.3' - name: flux2-kustomize-helm-example run: | ./bin/flux create source git flux-system \ From 207c50ceac000ae9b6583d44e7727ba90e507790 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Mon, 11 Jan 2021 21:40:39 +0100 Subject: [PATCH 48/54] Deprecate arch flags in favor of multi-arch images This commit deprecates the architecture flag (`--arch`) for the install and bootstrap commands, in favor of the bundled multi-arch images that will be available for the next MINOR range of GOTK controller releases. Summary of changes: * `*Arch` variables have been marked as deprecated for both commands. * `-arm64` suffix is no longer selectively added to the image definition of a component's `Deployment`. * `kubernetes.io/arch` node selector with the defined value has been removed from the components' `Deployment`s. * `Arch` has been removed from the available `Options` in `manifestgen/install`. * Documentation references have been changed to highlight existence of multi-arch images and supported architectures. Signed-off-by: Hidde Beydals --- cmd/flux/bootstrap.go | 4 ++-- cmd/flux/install.go | 6 +++--- docs/cmd/flux_bootstrap.md | 1 - docs/cmd/flux_bootstrap_github.md | 1 - docs/cmd/flux_bootstrap_gitlab.md | 1 - docs/cmd/flux_install.md | 1 - docs/get-started/index.md | 8 ++++---- docs/guides/installation.md | 8 ++++---- pkg/manifestgen/install/options.go | 2 -- pkg/manifestgen/install/templates.go | 6 ------ 10 files changed, 13 insertions(+), 25 deletions(-) diff --git a/cmd/flux/bootstrap.go b/cmd/flux/bootstrap.go index 9bccfdd4..5e70e3a2 100644 --- a/cmd/flux/bootstrap.go +++ b/cmd/flux/bootstrap.go @@ -55,7 +55,7 @@ var ( bootstrapWatchAllNamespaces bool bootstrapNetworkPolicy bool bootstrapManifestsPath string - bootstrapArch = flags.Arch(defaults.Arch) + bootstrapArch flags.Arch bootstrapLogLevel = flags.LogLevel(defaults.LogLevel) bootstrapRequiredComponents = []string{"source-controller", "kustomize-controller"} bootstrapTokenAuth bool @@ -90,6 +90,7 @@ func init() { bootstrapCmd.PersistentFlags().StringVar(&bootstrapManifestsPath, "manifests", "", "path to the manifest directory") bootstrapCmd.PersistentFlags().StringVar(&bootstrapClusterDomain, "cluster-domain", defaults.ClusterDomain, "internal cluster domain") bootstrapCmd.PersistentFlags().MarkHidden("manifests") + bootstrapCmd.PersistentFlags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64") rootCmd.AddCommand(bootstrapCmd) } @@ -120,7 +121,6 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes Components: bootstrapComponents(), Registry: bootstrapRegistry, ImagePullSecret: bootstrapImagePullSecret, - Arch: bootstrapArch.String(), WatchAllNamespaces: bootstrapWatchAllNamespaces, NetworkPolicy: bootstrapNetworkPolicy, LogLevel: bootstrapLogLevel.String(), diff --git a/cmd/flux/install.go b/cmd/flux/install.go index 2550976c..89ec0795 100644 --- a/cmd/flux/install.go +++ b/cmd/flux/install.go @@ -62,7 +62,7 @@ var ( installImagePullSecret string installWatchAllNamespaces bool installNetworkPolicy bool - installArch = flags.Arch(defaults.Arch) + installArch flags.Arch installLogLevel = flags.LogLevel(defaults.LogLevel) installClusterDomain string ) @@ -79,7 +79,6 @@ func init() { installCmd.Flags().StringSliceVar(&installExtraComponents, "components-extra", nil, "list of components in addition to those supplied or defaulted, accepts comma-separated values") installCmd.Flags().StringVar(&installManifestsPath, "manifests", "", "path to the manifest directory") - installCmd.Flags().MarkHidden("manifests") installCmd.Flags().StringVar(&installRegistry, "registry", defaults.Registry, "container registry where the toolkit images are published") installCmd.Flags().StringVar(&installImagePullSecret, "image-pull-secret", "", @@ -91,6 +90,8 @@ func init() { installCmd.Flags().BoolVar(&installNetworkPolicy, "network-policy", defaults.NetworkPolicy, "deny ingress access to the toolkit controllers from other namespaces using network policies") installCmd.Flags().StringVar(&installClusterDomain, "cluster-domain", defaults.ClusterDomain, "internal cluster domain") + installCmd.Flags().MarkHidden("manifests") + installCmd.Flags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64") rootCmd.AddCommand(installCmd) } @@ -121,7 +122,6 @@ func installCmdRun(cmd *cobra.Command, args []string) error { Components: components, Registry: installRegistry, ImagePullSecret: installImagePullSecret, - Arch: installArch.String(), WatchAllNamespaces: installWatchAllNamespaces, NetworkPolicy: installNetworkPolicy, LogLevel: installLogLevel.String(), diff --git a/docs/cmd/flux_bootstrap.md b/docs/cmd/flux_bootstrap.md index 7d180c5f..465edd96 100644 --- a/docs/cmd/flux_bootstrap.md +++ b/docs/cmd/flux_bootstrap.md @@ -9,7 +9,6 @@ The bootstrap sub-commands bootstrap the toolkit components on the targeted Git ### Options ``` - --arch arch cluster architecture, available options are: (amd64, arm, arm64) (default amd64) --branch string default branch (for GitHub this must match the default branch setting for the organization) (default "main") --cluster-domain string internal cluster domain (default "cluster.local") --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller]) diff --git a/docs/cmd/flux_bootstrap_github.md b/docs/cmd/flux_bootstrap_github.md index 784579e3..54c2fa67 100644 --- a/docs/cmd/flux_bootstrap_github.md +++ b/docs/cmd/flux_bootstrap_github.md @@ -61,7 +61,6 @@ flux bootstrap github [flags] ### Options inherited from parent commands ``` - --arch arch cluster architecture, available options are: (amd64, arm, arm64) (default amd64) --branch string default branch (for GitHub this must match the default branch setting for the organization) (default "main") --cluster-domain string internal cluster domain (default "cluster.local") --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller]) diff --git a/docs/cmd/flux_bootstrap_gitlab.md b/docs/cmd/flux_bootstrap_gitlab.md index 3df2f26e..a718b648 100644 --- a/docs/cmd/flux_bootstrap_gitlab.md +++ b/docs/cmd/flux_bootstrap_gitlab.md @@ -57,7 +57,6 @@ flux bootstrap gitlab [flags] ### Options inherited from parent commands ``` - --arch arch cluster architecture, available options are: (amd64, arm, arm64) (default amd64) --branch string default branch (for GitHub this must match the default branch setting for the organization) (default "main") --cluster-domain string internal cluster domain (default "cluster.local") --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller]) diff --git a/docs/cmd/flux_install.md b/docs/cmd/flux_install.md index 6604ab9e..4765e8e0 100644 --- a/docs/cmd/flux_install.md +++ b/docs/cmd/flux_install.md @@ -31,7 +31,6 @@ flux install [flags] ### Options ``` - --arch arch cluster architecture, available options are: (amd64, arm, arm64) (default amd64) --cluster-domain string internal cluster domain (default "cluster.local") --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller]) --components-extra strings list of components in addition to those supplied or defaulted, accepts comma-separated values diff --git a/docs/get-started/index.md b/docs/get-started/index.md index a038be65..b5e6226a 100644 --- a/docs/get-started/index.md +++ b/docs/get-started/index.md @@ -92,10 +92,10 @@ flux bootstrap github \ --personal ``` -!!! hint "ARM" - When deploying to a Kubernetes cluster with ARM architecture, - you can use `--arch=arm` for ARMv7 32-bit container images - and `--arch=arm64` for ARMv8 64-bit container images. +!!! hint "Multi-arch images" +The component images are published as [multi-arch container images](https://docs.docker.com/docker-for-mac/multi-arch/) +with support for Linux `amd64`, `arm64` and `armv7` (e.g. 32bit Raspberry Pi) +architectures. The bootstrap command creates a repository if one doesn't exist, commits the manifests for the Flux components to the default branch at the specified path, diff --git a/docs/guides/installation.md b/docs/guides/installation.md index 015e7779..67e2a7f2 100644 --- a/docs/guides/installation.md +++ b/docs/guides/installation.md @@ -62,10 +62,10 @@ flux bootstrap \ --version=latest ``` -!!! hint "ARM" - When deploying to a Kubernetes cluster with ARM architecture, - you can use `--arch=arm` for ARMv7 32-bit container images - and `--arch=arm64` for ARMv8 64-bit container images. +!!! hint "Multi-arch images" +The component images are published as [multi-arch container images](https://docs.docker.com/docker-for-mac/multi-arch/) +with support for Linux `amd64`, `arm64` and `armv7` (e.g. 32bit Raspberry Pi) +architectures. If you wish to install a specific version, use the Flux [release tag](https://github.com/fluxcd/flux2/releases) e.g. `--version=v0.2.0`. diff --git a/pkg/manifestgen/install/options.go b/pkg/manifestgen/install/options.go index 6689a7f7..0a15f821 100644 --- a/pkg/manifestgen/install/options.go +++ b/pkg/manifestgen/install/options.go @@ -27,7 +27,6 @@ type Options struct { EventsAddr string Registry string ImagePullSecret string - Arch string WatchAllNamespaces bool NetworkPolicy bool LogLevel string @@ -47,7 +46,6 @@ func MakeDefaultOptions() Options { EventsAddr: "", Registry: "ghcr.io/fluxcd", ImagePullSecret: "", - Arch: "amd64", WatchAllNamespaces: true, NetworkPolicy: true, LogLevel: "info", diff --git a/pkg/manifestgen/install/templates.go b/pkg/manifestgen/install/templates.go index 543b2b33..e870f4f6 100644 --- a/pkg/manifestgen/install/templates.go +++ b/pkg/manifestgen/install/templates.go @@ -28,7 +28,6 @@ var kustomizationTmpl = `--- {{- $eventsAddr := .EventsAddr }} {{- $watchAllNamespaces := .WatchAllNamespaces }} {{- $registry := .Registry }} -{{- $arch := .Arch }} {{- $logLevel := .LogLevel }} {{- $clusterDomain := .ClusterDomain }} apiVersion: kustomize.config.k8s.io/v1beta1 @@ -110,11 +109,7 @@ patchesJson6902: images: {{- range $i, $component := .Components }} - name: fluxcd/{{$component}} -{{- if eq $arch "amd64" }} newName: {{$registry}}/{{$component}} -{{- else }} - newName: {{$registry}}/{{$component}}-arm64 -{{- end }} {{- end }} {{- end }} ` @@ -136,7 +131,6 @@ spec: template: spec: nodeSelector: - kubernetes.io/arch: {{.Arch}} kubernetes.io/os: linux {{- if .ImagePullSecret }} imagePullSecrets: From f6fa468acb133a72f43c48a54cbbb1ed5e64aed6 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 15 Jan 2021 12:39:28 +0200 Subject: [PATCH 49/54] Use reconcile request annotation Replace deprecated `ReconcileAtAnnotation` annotation with `ReconcileRequestAnnotation` Signed-off-by: Stefan Prodan --- cmd/flux/reconcile.go | 4 ++-- cmd/flux/reconcile_alert.go | 4 ++-- cmd/flux/reconcile_alertprovider.go | 4 ++-- cmd/flux/reconcile_helmrelease.go | 4 ++-- cmd/flux/reconcile_kustomization.go | 4 ++-- cmd/flux/reconcile_receiver.go | 4 ++-- cmd/flux/reconcile_source_bucket.go | 4 ++-- cmd/flux/reconcile_source_git.go | 4 ++-- cmd/flux/reconcile_source_helm.go | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cmd/flux/reconcile.go b/cmd/flux/reconcile.go index c348be0d..25a5e0cf 100644 --- a/cmd/flux/reconcile.go +++ b/cmd/flux/reconcile.go @@ -131,10 +131,10 @@ func requestReconciliation(ctx context.Context, kubeClient client.Client, } if ann := obj.GetAnnotations(); ann == nil { obj.SetAnnotations(map[string]string{ - meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), + meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), }) } else { - ann[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) + ann[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) obj.SetAnnotations(ann) } return kubeClient.Update(ctx, obj.asClientObject()) diff --git a/cmd/flux/reconcile_alert.go b/cmd/flux/reconcile_alert.go index d5acf204..f37ed870 100644 --- a/cmd/flux/reconcile_alert.go +++ b/cmd/flux/reconcile_alert.go @@ -77,10 +77,10 @@ func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error { logger.Actionf("annotating Alert %s in %s namespace", name, namespace) if alert.Annotations == nil { alert.Annotations = map[string]string{ - meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), + meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), } } else { - alert.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) + alert.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) } if err := kubeClient.Update(ctx, &alert); err != nil { diff --git a/cmd/flux/reconcile_alertprovider.go b/cmd/flux/reconcile_alertprovider.go index 3b76bf00..67f12cd0 100644 --- a/cmd/flux/reconcile_alertprovider.go +++ b/cmd/flux/reconcile_alertprovider.go @@ -73,10 +73,10 @@ func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error { if alertProvider.Annotations == nil { alertProvider.Annotations = map[string]string{ - meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), + meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), } } else { - alertProvider.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) + alertProvider.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) } if err := kubeClient.Update(ctx, &alertProvider); err != nil { return err diff --git a/cmd/flux/reconcile_helmrelease.go b/cmd/flux/reconcile_helmrelease.go index 80c8c575..edbb3671 100644 --- a/cmd/flux/reconcile_helmrelease.go +++ b/cmd/flux/reconcile_helmrelease.go @@ -153,10 +153,10 @@ func requestHelmReleaseReconciliation(ctx context.Context, kubeClient client.Cli } if helmRelease.Annotations == nil { helmRelease.Annotations = map[string]string{ - meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), + meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), } } else { - helmRelease.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) + helmRelease.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) } return kubeClient.Update(ctx, helmRelease) }) diff --git a/cmd/flux/reconcile_kustomization.go b/cmd/flux/reconcile_kustomization.go index 109096a3..b543ed4d 100644 --- a/cmd/flux/reconcile_kustomization.go +++ b/cmd/flux/reconcile_kustomization.go @@ -142,10 +142,10 @@ func requestKustomizeReconciliation(ctx context.Context, kubeClient client.Clien } if kustomization.Annotations == nil { kustomization.Annotations = map[string]string{ - meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), + meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), } } else { - kustomization.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) + kustomization.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) } return kubeClient.Update(ctx, kustomization) }) diff --git a/cmd/flux/reconcile_receiver.go b/cmd/flux/reconcile_receiver.go index f89da3dc..b26b55f2 100644 --- a/cmd/flux/reconcile_receiver.go +++ b/cmd/flux/reconcile_receiver.go @@ -77,10 +77,10 @@ func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error { logger.Actionf("annotating Receiver %s in %s namespace", name, namespace) if receiver.Annotations == nil { receiver.Annotations = map[string]string{ - meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), + meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), } } else { - receiver.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) + receiver.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) } if err := kubeClient.Update(ctx, &receiver); err != nil { return err diff --git a/cmd/flux/reconcile_source_bucket.go b/cmd/flux/reconcile_source_bucket.go index 2c20598e..22928927 100644 --- a/cmd/flux/reconcile_source_bucket.go +++ b/cmd/flux/reconcile_source_bucket.go @@ -145,10 +145,10 @@ func requestBucketReconciliation(ctx context.Context, kubeClient client.Client, } if bucket.Annotations == nil { bucket.Annotations = map[string]string{ - meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), + meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), } } else { - bucket.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) + bucket.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) } return kubeClient.Update(ctx, bucket) }) diff --git a/cmd/flux/reconcile_source_git.go b/cmd/flux/reconcile_source_git.go index d6dfcd8e..b5e54e75 100644 --- a/cmd/flux/reconcile_source_git.go +++ b/cmd/flux/reconcile_source_git.go @@ -116,10 +116,10 @@ func requestGitRepositoryReconciliation(ctx context.Context, kubeClient client.C } if repository.Annotations == nil { repository.Annotations = map[string]string{ - meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), + meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), } } else { - repository.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) + repository.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) } return kubeClient.Update(ctx, repository) }) diff --git a/cmd/flux/reconcile_source_helm.go b/cmd/flux/reconcile_source_helm.go index 782afcba..4e52d172 100644 --- a/cmd/flux/reconcile_source_helm.go +++ b/cmd/flux/reconcile_source_helm.go @@ -117,10 +117,10 @@ func requestHelmRepositoryReconciliation(ctx context.Context, kubeClient client. } if repository.Annotations == nil { repository.Annotations = map[string]string{ - meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), + meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano), } } else { - repository.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) + repository.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano) } return kubeClient.Update(ctx, repository) }) From c7080d2834700529f0a816fec1771c1e5eee5a00 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Mon, 11 Jan 2021 11:30:43 +0100 Subject: [PATCH 50/54] Configure `project_name` for GoReleaser This causes the format of the checksum file generated during the release to change from `flux2_*_checksums.txt` to `flux_*_checksums.txt`. The configuration change is made through `project_name` and not via the `checksum.name_template` setting, because a single checksum file is generated during the release process. The download and/or installation script in `install/flux.sh` has been adapted to assume the new filename starting with MINOR version `0.6.0`. Signed-off-by: Hidde Beydals --- .goreleaser.yml | 1 + install/flux.sh | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 8a6de6a0..dd0e4b69 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,4 @@ +project_name: flux builds: - <<: &build_defaults binary: flux diff --git a/install/flux.sh b/install/flux.sh index 83c727cb..1394b97c 100755 --- a/install/flux.sh +++ b/install/flux.sh @@ -76,7 +76,7 @@ setup_tmp() { TMP_HASH="${TMP_DIR}/flux.hash" TMP_BIN="${TMP_DIR}/flux.tar.gz" cleanup() { - code=$? + local code=$? set +e trap - EXIT rm -rf "${TMP_DIR}" @@ -120,9 +120,51 @@ download() { [[ $? -eq 0 ]] || fatal 'Download failed' } +# Version comparison +# Returns 0 on '=', 1 on '>', and 2 on '<'. +# Ref: https://stackoverflow.com/a/4025065 +vercomp () { + if [[ $1 == $2 ]] + then + return 0 + fi + local IFS=. + local i ver1=($1) ver2=($2) + # fill empty fields in ver1 with zeros + for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)) + do + ver1[i]=0 + done + for ((i=0; i<${#ver1[@]}; i++)) + do + if [[ -z ${ver2[i]} ]] + then + # fill empty fields in ver2 with zeros + ver2[i]=0 + fi + if ((10#${ver1[i]} > 10#${ver2[i]})) + then + return 1 + fi + if ((10#${ver1[i]} < 10#${ver2[i]})) + then + return 2 + fi + done + return 0 +} + # Download hash from Github URL download_hash() { - HASH_URL="https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_FLUX}/flux2_${VERSION_FLUX}_checksums.txt" + HASH_URL="https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_FLUX}/flux_${VERSION_FLUX}_checksums.txt" + # NB: support the checksum filename format prior to v0.6.0 + set +e + vercomp ${VERSION_FLUX} 0.6.0 + if [[ $? -eq 2 ]]; then + HASH_URL="https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_FLUX}/flux2_${VERSION_FLUX}_checksums.txt" + fi + set -e + info "Downloading hash ${HASH_URL}" download "${TMP_HASH}" "${HASH_URL}" HASH_EXPECTED=$(grep " flux_${VERSION_FLUX}_${OS}_${ARCH}.tar.gz$" "${TMP_HASH}") From a3b9c094b63af0c656193c361b3e6474e68a7217 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Fri, 15 Jan 2021 11:53:11 +0100 Subject: [PATCH 51/54] docs: styling of hint blocks Signed-off-by: Hidde Beydals --- docs/get-started/index.md | 6 +++--- docs/guides/installation.md | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/get-started/index.md b/docs/get-started/index.md index b5e6226a..737ba594 100644 --- a/docs/get-started/index.md +++ b/docs/get-started/index.md @@ -93,9 +93,9 @@ flux bootstrap github \ ``` !!! hint "Multi-arch images" -The component images are published as [multi-arch container images](https://docs.docker.com/docker-for-mac/multi-arch/) -with support for Linux `amd64`, `arm64` and `armv7` (e.g. 32bit Raspberry Pi) -architectures. + The component images are published as [multi-arch container images](https://docs.docker.com/docker-for-mac/multi-arch/) + with support for Linux `amd64`, `arm64` and `armv7` (e.g. 32bit Raspberry Pi) + architectures. The bootstrap command creates a repository if one doesn't exist, commits the manifests for the Flux components to the default branch at the specified path, diff --git a/docs/guides/installation.md b/docs/guides/installation.md index 67e2a7f2..58001f71 100644 --- a/docs/guides/installation.md +++ b/docs/guides/installation.md @@ -63,9 +63,9 @@ flux bootstrap \ ``` !!! hint "Multi-arch images" -The component images are published as [multi-arch container images](https://docs.docker.com/docker-for-mac/multi-arch/) -with support for Linux `amd64`, `arm64` and `armv7` (e.g. 32bit Raspberry Pi) -architectures. + The component images are published as [multi-arch container images](https://docs.docker.com/docker-for-mac/multi-arch/) + with support for Linux `amd64`, `arm64` and `armv7` (e.g. 32bit Raspberry Pi) + architectures. If you wish to install a specific version, use the Flux [release tag](https://github.com/fluxcd/flux2/releases) e.g. `--version=v0.2.0`. @@ -118,11 +118,11 @@ flux bootstrap github \ --personal ``` -!!! hint "Deploy Key" - The bootstrap command creates a ssh key which it stores as a secret in the +!!! hint "Deploy key" + The bootstrap command creates an SSH key which it stores as a secret in the Kubernetes cluster. The key is also used to create a deploy key in the GitHub repository. The new deploy key will be linked to the personal access token used - to authenticate. Removing the personal access token will remove the deploy key. + to authenticate. **Removing the personal access token will also remove the deploy key.** Run the bootstrap for a repository owned by a GitHub organization: From db611549f2c940c28dd7f3264c52a59527adbec5 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Thu, 14 Jan 2021 13:58:16 +0100 Subject: [PATCH 52/54] Add GCP docs for Mozilla SOPS Signed-off-by: Somtochi Onyekwere --- docs/guides/mozilla-sops.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/guides/mozilla-sops.md b/docs/guides/mozilla-sops.md index ed9f9848..8e85f496 100644 --- a/docs/guides/mozilla-sops.md +++ b/docs/guides/mozilla-sops.md @@ -104,13 +104,15 @@ flux create kustomization my-secrets \ Note that the `sops-gpg` can contain more than one key, sops will try to decrypt the secrets by iterating over all the private keys until it finds one that works. -### AWS/Azure/GCP +### Using various cloud providers When using AWS/GCP KMS, you'll have to bind an IAM Role with access to the KMS keys to the `default` service account of the `flux-system` namespace for kustomize-controller to be able to fetch keys from KMS. -AWS IAM Role example: +#### AWS + +IAM Role example: ```json { @@ -131,10 +133,27 @@ AWS IAM Role example: } ``` +#### Azure + When using Azure Key Vault you need to authenticate the kustomize controller either by passing [Service Principal credentials as environment variables](https://github.com/mozilla/sops#encrypting-using-azure-key-vault) or with [add-pod-identity](https://github.com/Azure/aad-pod-identity). +#### Google Cloud + +Please ensure that the GKE cluster has Workload Identity enabled. + +1. Create a service account with the role `Cloud KMS CryptoKey Encrypter/Decrypter`. +2. Create an IAM policy binding between the GCP service account to the `default` service account of the `flux-system`. +3. Annotate the `default` service account in the `flux-system` with the GCP service account. + +```sh +kubectl annotate serviceaccount \ + --namespace flux-system \ + default \ + iam.gke.io/gcp-service-account=@project-id.iam.gserviceaccount.com +``` + ## GitOps workflow A cluster admin should create the Kubernetes secret with the PGP keys on each cluster and From 2f0835b6559704e4b2caf5e70f1de5f4930798bb Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 15 Jan 2021 13:16:53 +0200 Subject: [PATCH 53/54] Make branch arg required for image updates Signed-off-by: Stefan Prodan --- cmd/flux/create_image_updateauto.go | 6 +++++- docs/cmd/flux_create_image_update.md | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/flux/create_image_updateauto.go b/cmd/flux/create_image_updateauto.go index 0f1935f3..4379ed4d 100644 --- a/cmd/flux/create_image_updateauto.go +++ b/cmd/flux/create_image_updateauto.go @@ -50,7 +50,7 @@ var imageUpdateArgs = imageUpdateFlags{} func init() { flags := createImageUpdateCmd.Flags() flags.StringVar(&imageUpdateArgs.gitRepoRef, "git-repo-ref", "", "the name of a GitRepository resource with details of the upstream git repository") - flags.StringVar(&imageUpdateArgs.branch, "branch", "", "the branch to push commits to") + flags.StringVar(&imageUpdateArgs.branch, "branch", "", "the branch to checkout and push commits to") flags.StringVar(&imageUpdateArgs.commitTemplate, "commit-template", "", "a template for commit messages") flags.StringVar(&imageUpdateArgs.authorName, "author-name", "", "the name to use for commit author") flags.StringVar(&imageUpdateArgs.authorEmail, "author-email", "", "the email to use for commit author") @@ -68,6 +68,10 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("a reference to a GitRepository is required (--git-repo-ref)") } + if imageUpdateArgs.branch == "" { + return fmt.Errorf("the Git repoistory branch is required (--branch)") + } + labels, err := parseLabels() if err != nil { return err diff --git a/docs/cmd/flux_create_image_update.md b/docs/cmd/flux_create_image_update.md index 7d96fa74..c5ea5140 100644 --- a/docs/cmd/flux_create_image_update.md +++ b/docs/cmd/flux_create_image_update.md @@ -17,7 +17,7 @@ flux create image update [flags] ``` --author-email string the email to use for commit author --author-name string the name to use for commit author - --branch string the branch to push commits to + --branch string the branch to checkout and push commits to --commit-template string a template for commit messages --git-repo-ref string the name of a GitRepository resource with details of the upstream git repository -h, --help help for update From bcdce02b7815532f3ef0c09ad4e973dd40cc9e36 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Fri, 15 Jan 2021 13:18:04 +0200 Subject: [PATCH 54/54] Add image tags regex filter arg to policy command Signed-off-by: Stefan Prodan --- cmd/flux/create_image_policy.go | 12 ++++++++++-- docs/cmd/flux_create_image_policy.md | 7 ++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/cmd/flux/create_image_policy.go b/cmd/flux/create_image_policy.go index 1c77113c..287e3dcd 100644 --- a/cmd/flux/create_image_policy.go +++ b/cmd/flux/create_image_policy.go @@ -38,8 +38,9 @@ the status of the object.`, RunE: createImagePolicyRun} type imagePolicyFlags struct { - imageRef string - semver string + imageRef string + semver string + filterRegex string } var imagePolicyArgs = imagePolicyFlags{} @@ -48,6 +49,7 @@ func init() { flags := createImagePolicyCmd.Flags() flags.StringVar(&imagePolicyArgs.imageRef, "image-ref", "", "the name of an image repository object") flags.StringVar(&imagePolicyArgs.semver, "semver", "", "a semver range to apply to tags; e.g., '1.x'") + flags.StringVar(&imagePolicyArgs.filterRegex, "filter-regex", "", " regular expression pattern used to filter the image tags") createImageCmd.AddCommand(createImagePolicyCmd) } @@ -95,6 +97,12 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("a policy must be provided with --semver") } + if imagePolicyArgs.filterRegex != "" { + policy.Spec.FilterTags = &imagev1.TagFilter{ + Pattern: imagePolicyArgs.filterRegex, + } + } + if export { return printExport(exportImagePolicy(&policy)) } diff --git a/docs/cmd/flux_create_image_policy.md b/docs/cmd/flux_create_image_policy.md index c311ed16..5a9db5f6 100644 --- a/docs/cmd/flux_create_image_policy.md +++ b/docs/cmd/flux_create_image_policy.md @@ -18,9 +18,10 @@ flux create image policy [flags] ### Options ``` - -h, --help help for policy - --image-ref string the name of an image repository object - --semver string a semver range to apply to tags; e.g., '1.x' + --filter-regex string regular expression pattern used to filter the image tags + -h, --help help for policy + --image-ref string the name of an image repository object + --semver string a semver range to apply to tags; e.g., '1.x' ``` ### Options inherited from parent commands