Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cff96ed7ca | ||
|
|
4e8a600f34 | ||
|
|
4fd5684277 | ||
|
|
06bf469ba7 | ||
|
|
b8a215230c | ||
|
|
2460cfcf1c | ||
|
|
364242c857 | ||
|
|
29e2900f59 | ||
|
|
61e1fb770e | ||
|
|
2d3fcbdea3 | ||
|
|
47e15cee3d | ||
|
|
adeb3e3f42 | ||
|
|
fb1278285b | ||
|
|
e371610849 | ||
|
|
424de63bd1 | ||
|
|
832c925d39 | ||
|
|
378f118d51 | ||
|
|
d651777122 | ||
|
|
65d8ebabb8 | ||
|
|
9195ed9a1b | ||
|
|
5df8f7313c | ||
|
|
25ed6ca0a4 | ||
|
|
9f972995bd | ||
|
|
29c46a9892 | ||
|
|
ef579fe596 | ||
|
|
5b268f62a3 | ||
|
|
1f1c8286a5 | ||
|
|
5401e1ace4 | ||
|
|
69294ef56d | ||
|
|
a685ed8029 | ||
|
|
68d0be3818 |
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve Flux v2
|
||||||
|
title: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Find out more about your support options and getting help at
|
||||||
|
|
||||||
|
https://fluxcd.io/support/
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Describe the bug
|
||||||
|
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
### To Reproduce
|
||||||
|
|
||||||
|
Steps to reproduce the behaviour:
|
||||||
|
|
||||||
|
1. Provide Flux install instructions
|
||||||
|
2. Provide a GitHub repository with Kubernetes manifests
|
||||||
|
|
||||||
|
### Expected behavior
|
||||||
|
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
### Additional context
|
||||||
|
|
||||||
|
- Kubernetes version:
|
||||||
|
- Git provider:
|
||||||
|
- Container registry provider:
|
||||||
|
|
||||||
|
Below please provide the output of the following commands:
|
||||||
|
|
||||||
|
```cli
|
||||||
|
flux --version
|
||||||
|
flux check
|
||||||
|
kubectl -n <namespace> get all
|
||||||
|
kubectl -n <namespace> logs deploy/source-controller
|
||||||
|
kubectl -n <namespace> logs deploy/kustomize-controller
|
||||||
|
```
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Ask a question
|
||||||
|
url: https://github.com/fluxcd/flux2/discussions
|
||||||
|
about: Please ask and answer questions here.
|
||||||
5
.github/kind/config.yaml
vendored
Normal file
5
.github/kind/config.yaml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
kind: Cluster
|
||||||
|
apiVersion: kind.x-k8s.io/v1alpha4
|
||||||
|
networking:
|
||||||
|
disableDefaultCNI: true # disable kindnet
|
||||||
|
podSubnet: 192.168.0.0/16 # set to Calico's default subnet
|
||||||
17
.github/workflows/e2e.yaml
vendored
17
.github/workflows/e2e.yaml
vendored
@@ -26,7 +26,13 @@ jobs:
|
|||||||
- name: Setup Kubernetes
|
- name: Setup Kubernetes
|
||||||
uses: engineerd/setup-kind@v0.5.0
|
uses: engineerd/setup-kind@v0.5.0
|
||||||
with:
|
with:
|
||||||
image: kindest/node:v1.16.9
|
version: "v0.10.0"
|
||||||
|
image: kindest/node:v1.20.2@sha256:8f7ea6e7642c0da54f04a7ee10431549c0257315b3a634f6ef2fecaaedb19bab
|
||||||
|
config: .github/kind/config.yaml # disable KIND-net
|
||||||
|
- name: Setup Calico for network policy
|
||||||
|
run: |
|
||||||
|
kubectl apply -f https://docs.projectcalico.org/v3.16/manifests/calico.yaml
|
||||||
|
kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
|
||||||
- name: Run test
|
- name: Run test
|
||||||
run: make test
|
run: make test
|
||||||
- name: Check if working tree is dirty
|
- name: Check if working tree is dirty
|
||||||
@@ -44,6 +50,15 @@ jobs:
|
|||||||
- name: flux install --manifests
|
- name: flux install --manifests
|
||||||
run: |
|
run: |
|
||||||
./bin/flux install --manifests ./manifests/install/
|
./bin/flux install --manifests ./manifests/install/
|
||||||
|
- name: flux create secret
|
||||||
|
run: |
|
||||||
|
./bin/flux create secret git git-ssh-test \
|
||||||
|
--url ssh://git@github.com/stefanprodan/podinfo
|
||||||
|
./bin/flux create secret git git-https-test \
|
||||||
|
--url https://github.com/stefanprodan/podinfo \
|
||||||
|
--username=test --password=test
|
||||||
|
./bin/flux create secret helm helm-test \
|
||||||
|
--username=test --password=test
|
||||||
- name: flux create source git
|
- name: flux create source git
|
||||||
run: |
|
run: |
|
||||||
./bin/flux create source git podinfo \
|
./bin/flux create source git podinfo \
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -66,6 +66,8 @@ the following guides:
|
|||||||
- [Setup Notifications](https://toolkit.fluxcd.io/guides/notifications/)
|
- [Setup Notifications](https://toolkit.fluxcd.io/guides/notifications/)
|
||||||
- [Setup Webhook Receivers](https://toolkit.fluxcd.io/guides/webhook-receivers/)
|
- [Setup Webhook Receivers](https://toolkit.fluxcd.io/guides/webhook-receivers/)
|
||||||
|
|
||||||
|
If you should need help, please refer to our **[Support page](https://fluxcd.io/support/)**.
|
||||||
|
|
||||||
## GitOps Toolkit
|
## GitOps Toolkit
|
||||||
|
|
||||||
The GitOps Toolkit is the set of APIs and controllers that make up the
|
The GitOps Toolkit is the set of APIs and controllers that make up the
|
||||||
@@ -97,15 +99,19 @@ guides](https://toolkit.fluxcd.io/dev-guides/source-watcher/).
|
|||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
The Flux project is always looking for new contributors and there are a multitude of ways to get involved.
|
Need help or want to contribute? Please see the links below. The Flux project is always looking for new contributors and there are a multitude of ways to get involved.
|
||||||
Depending on what you want to do, some of the following bits might be your first steps:
|
|
||||||
|
|
||||||
- Join our upcoming dev meetings ([meeting access and agenda](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view))
|
- Getting Started?
|
||||||
- Talk to us in the #flux channel on [CNCF Slack](https://slack.cncf.io/)
|
- Look at our [Get Started guide](https://toolkit.fluxcd.io/get-started/) and give us feedback
|
||||||
- Ask questions and propose features on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions)
|
- Need help?
|
||||||
- And if you are completely new to Flux and the GitOps Toolkit, take a look at our [Get Started guide](https://toolkit.fluxcd.io/get-started/) and give us feedback
|
- First: Ask questions on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions)
|
||||||
- To be part of the conversation about Flux's development, [join the flux-dev mailing list](https://lists.cncf.io/g/cncf-flux-dev).
|
- Second: Talk to us in the #flux channel on [CNCF Slack](https://slack.cncf.io/)
|
||||||
- Check out [how to contribute](CONTRIBUTING.md) to the project
|
- Please follow our [Support Guidelines](https://fluxcd.io/support/) (in short: be nice, be respectful of volunteers' time, understand that maintainers and contributors cannot respond to all DMs, and keep discussions in the public #flux channel as much as possible).
|
||||||
|
- Have feature proposals or want to contribute?
|
||||||
|
- Propose features on our [GH Discussions page](https://github.com/fluxcd/flux2/discussions)
|
||||||
|
- Join our upcoming dev meetings ([meeting access and agenda](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/view))
|
||||||
|
- [Join the flux-dev mailing list](https://lists.cncf.io/g/cncf-flux-dev).
|
||||||
|
- Check out [how to contribute](CONTRIBUTING.md) to the project
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Note that this action can only be used on GitHub **Linux AMD64** runners.
|
|||||||
|
|
||||||
### Automate Flux updates
|
### Automate Flux updates
|
||||||
|
|
||||||
Example workflow for updating Flux's components generated with `flux bootstrap --arch=amd64 --path=clusters/production`:
|
Example workflow for updating Flux's components generated with `flux bootstrap --path=clusters/production`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: update-flux
|
name: update-flux
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
- name: Check for updates
|
- name: Check for updates
|
||||||
id: update
|
id: update
|
||||||
run: |
|
run: |
|
||||||
flux install --arch=amd64 \
|
flux install \
|
||||||
--export > ./clusters/production/flux-system/gotk-components.yaml
|
--export > ./clusters/production/flux-system/gotk-components.yaml
|
||||||
|
|
||||||
VERSION="$(flux -v)"
|
VERSION="$(flux -v)"
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ func makeSecret(name string) (corev1.Secret, error) {
|
|||||||
Namespace: rootArgs.namespace,
|
Namespace: rootArgs.namespace,
|
||||||
Labels: secretLabels,
|
Labels: secretLabels,
|
||||||
},
|
},
|
||||||
|
StringData: map[string]string{},
|
||||||
|
Data: nil,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,10 +133,10 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
secret.Data = map[string][]byte{
|
secret.StringData = map[string]string{
|
||||||
"identity": pair.PrivateKey,
|
"identity": string(pair.PrivateKey),
|
||||||
"identity.pub": pair.PublicKey,
|
"identity.pub": string(pair.PublicKey),
|
||||||
"known_hosts": hostKey,
|
"known_hosts": string(hostKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !createArgs.export {
|
if !createArgs.export {
|
||||||
@@ -148,9 +148,9 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add cert data when it's implemented in source-controller
|
// TODO: add cert data when it's implemented in source-controller
|
||||||
secret.Data = map[string][]byte{
|
secret.StringData = map[string]string{
|
||||||
"username": []byte(secretGitArgs.username),
|
"username": secretGitArgs.username,
|
||||||
"password": []byte(secretGitArgs.password),
|
"password": secretGitArgs.password,
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
||||||
|
|||||||
@@ -17,15 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteHelmReleaseCmd = &cobra.Command{
|
var deleteHelmReleaseCmd = &cobra.Command{
|
||||||
@@ -36,57 +29,12 @@ var deleteHelmReleaseCmd = &cobra.Command{
|
|||||||
Example: ` # Delete a Helm release and the Kubernetes resources created by it
|
Example: ` # Delete a Helm release and the Kubernetes resources created by it
|
||||||
flux delete hr podinfo
|
flux delete hr podinfo
|
||||||
`,
|
`,
|
||||||
RunE: deleteHelmReleaseCmdRun,
|
RunE: deleteCommand{
|
||||||
|
apiType: helmReleaseType,
|
||||||
|
object: universalAdapter{&helmv2.HelmRelease{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
deleteCmd.AddCommand(deleteHelmReleaseCmd)
|
deleteCmd.AddCommand(deleteHelmReleaseCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("release name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
var helmRelease helmv2.HelmRelease
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deleteArgs.silent {
|
|
||||||
if !helmRelease.Spec.Suspend {
|
|
||||||
logger.Waitingf("This action will remove the Kubernetes objects previously applied by the %s Helm release!", name)
|
|
||||||
}
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: "Are you sure you want to delete this Helm release",
|
|
||||||
IsConfirm: true,
|
|
||||||
}
|
|
||||||
if _, err := prompt.Run(); err != nil {
|
|
||||||
return fmt.Errorf("aborting")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("deleting release %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
err = kubeClient.Delete(ctx, &helmRelease)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("release deleted")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,14 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteKsCmd = &cobra.Command{
|
var deleteKsCmd = &cobra.Command{
|
||||||
@@ -35,57 +29,12 @@ var deleteKsCmd = &cobra.Command{
|
|||||||
Example: ` # Delete a kustomization and the Kubernetes resources created by it
|
Example: ` # Delete a kustomization and the Kubernetes resources created by it
|
||||||
flux delete kustomization podinfo
|
flux delete kustomization podinfo
|
||||||
`,
|
`,
|
||||||
RunE: deleteKsCmdRun,
|
RunE: deleteCommand{
|
||||||
|
apiType: kustomizationType,
|
||||||
|
object: universalAdapter{&kustomizev1.Kustomization{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
deleteCmd.AddCommand(deleteKsCmd)
|
deleteCmd.AddCommand(deleteKsCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteKsCmdRun(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("kustomization name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
var kustomization kustomizev1.Kustomization
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &kustomization)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deleteArgs.silent {
|
|
||||||
if !kustomization.Spec.Suspend {
|
|
||||||
logger.Waitingf("This action will remove the Kubernetes objects previously applied by the %s kustomization!", name)
|
|
||||||
}
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: "Are you sure you want to delete this kustomization",
|
|
||||||
IsConfirm: true,
|
|
||||||
}
|
|
||||||
if _, err := prompt.Run(); err != nil {
|
|
||||||
return fmt.Errorf("aborting")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("deleting kustomization %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
err = kubeClient.Delete(ctx, &kustomization)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("kustomization deleted")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,14 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteSourceBucketCmd = &cobra.Command{
|
var deleteSourceBucketCmd = &cobra.Command{
|
||||||
@@ -34,54 +28,12 @@ var deleteSourceBucketCmd = &cobra.Command{
|
|||||||
Example: ` # Delete a Bucket source
|
Example: ` # Delete a Bucket source
|
||||||
flux delete source bucket podinfo
|
flux delete source bucket podinfo
|
||||||
`,
|
`,
|
||||||
RunE: deleteSourceBucketCmdRun,
|
RunE: deleteCommand{
|
||||||
|
apiType: bucketType,
|
||||||
|
object: universalAdapter{&sourcev1.Bucket{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
deleteSourceCmd.AddCommand(deleteSourceBucketCmd)
|
deleteSourceCmd.AddCommand(deleteSourceBucketCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
var bucket sourcev1.Bucket
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &bucket)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deleteArgs.silent {
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: "Are you sure you want to delete this source",
|
|
||||||
IsConfirm: true,
|
|
||||||
}
|
|
||||||
if _, err := prompt.Run(); err != nil {
|
|
||||||
return fmt.Errorf("aborting")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("deleting source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
err = kubeClient.Delete(ctx, &bucket)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("source deleted")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,14 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var deleteSourceGitCmd = &cobra.Command{
|
var deleteSourceGitCmd = &cobra.Command{
|
||||||
@@ -34,54 +28,12 @@ var deleteSourceGitCmd = &cobra.Command{
|
|||||||
Example: ` # Delete a Git repository
|
Example: ` # Delete a Git repository
|
||||||
flux delete source git podinfo
|
flux delete source git podinfo
|
||||||
`,
|
`,
|
||||||
RunE: deleteSourceGitCmdRun,
|
RunE: deleteCommand{
|
||||||
|
apiType: gitRepositoryType,
|
||||||
|
object: universalAdapter{&sourcev1.GitRepository{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
deleteSourceCmd.AddCommand(deleteSourceGitCmd)
|
deleteSourceCmd.AddCommand(deleteSourceGitCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("git name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
var git sourcev1.GitRepository
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &git)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deleteArgs.silent {
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: "Are you sure you want to delete this source",
|
|
||||||
IsConfirm: true,
|
|
||||||
}
|
|
||||||
if _, err := prompt.Run(); err != nil {
|
|
||||||
return fmt.Errorf("aborting")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("deleting source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
err = kubeClient.Delete(ctx, &git)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("source deleted")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ var deleteSourceHelmCmd = &cobra.Command{
|
|||||||
Example: ` # Delete a Helm repository
|
Example: ` # Delete a Helm repository
|
||||||
flux delete source helm podinfo
|
flux delete source helm podinfo
|
||||||
`,
|
`,
|
||||||
RunE: deleteSourceHelmCmdRun,
|
RunE: deleteCommand{
|
||||||
|
apiType: helmRepositoryType,
|
||||||
|
object: universalAdapter{&sourcev1.HelmRepository{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ var getSourceHelmChartCmd = &cobra.Command{
|
|||||||
flux get sources chart --all-namespaces
|
flux get sources chart --all-namespaces
|
||||||
`,
|
`,
|
||||||
RunE: getCommand{
|
RunE: getCommand{
|
||||||
apiType: bucketType,
|
apiType: helmChartType,
|
||||||
list: &helmChartListAdapter{&sourcev1.HelmChartList{}},
|
list: &helmChartListAdapter{&sourcev1.HelmChartList{}},
|
||||||
}.run,
|
}.run,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ var getSourceGitCmd = &cobra.Command{
|
|||||||
flux get sources git --all-namespaces
|
flux get sources git --all-namespaces
|
||||||
`,
|
`,
|
||||||
RunE: getCommand{
|
RunE: getCommand{
|
||||||
apiType: bucketType,
|
apiType: gitRepositoryType,
|
||||||
list: &gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},
|
list: &gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},
|
||||||
}.run,
|
}.run,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ var getSourceHelmCmd = &cobra.Command{
|
|||||||
flux get sources helm --all-namespaces
|
flux get sources helm --all-namespaces
|
||||||
`,
|
`,
|
||||||
RunE: getCommand{
|
RunE: getCommand{
|
||||||
apiType: bucketType,
|
apiType: helmRepositoryType,
|
||||||
list: &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},
|
list: &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},
|
||||||
}.run,
|
}.run,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,17 +93,31 @@ func reconcileHrCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if rhrArgs.syncHrWithSource {
|
if rhrArgs.syncHrWithSource {
|
||||||
|
nsCopy := rootArgs.namespace
|
||||||
|
if helmRelease.Spec.Chart.Spec.SourceRef.Namespace != "" {
|
||||||
|
rootArgs.namespace = helmRelease.Spec.Chart.Spec.SourceRef.Namespace
|
||||||
|
}
|
||||||
switch helmRelease.Spec.Chart.Spec.SourceRef.Kind {
|
switch helmRelease.Spec.Chart.Spec.SourceRef.Kind {
|
||||||
case sourcev1.HelmRepositoryKind:
|
case sourcev1.HelmRepositoryKind:
|
||||||
err = reconcileSourceHelmCmdRun(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
|
err = reconcileCommand{
|
||||||
|
apiType: helmRepositoryType,
|
||||||
|
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
|
||||||
|
}.run(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
|
||||||
case sourcev1.GitRepositoryKind:
|
case sourcev1.GitRepositoryKind:
|
||||||
err = reconcileSourceGitCmdRun(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
|
err = reconcileCommand{
|
||||||
|
apiType: gitRepositoryType,
|
||||||
|
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
|
||||||
|
}.run(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
|
||||||
case sourcev1.BucketKind:
|
case sourcev1.BucketKind:
|
||||||
err = reconcileSourceBucketCmdRun(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
|
err = reconcileCommand{
|
||||||
|
apiType: bucketType,
|
||||||
|
object: bucketAdapter{&sourcev1.Bucket{}},
|
||||||
|
}.run(nil, []string{helmRelease.Spec.Chart.Spec.SourceRef.Name})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
rootArgs.namespace = nsCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
lastHandledReconcileAt := helmRelease.Status.LastHandledReconcileAt
|
lastHandledReconcileAt := helmRelease.Status.LastHandledReconcileAt
|
||||||
|
|||||||
@@ -91,15 +91,26 @@ func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if rksArgs.syncKsWithSource {
|
if rksArgs.syncKsWithSource {
|
||||||
|
nsCopy := rootArgs.namespace
|
||||||
|
if kustomization.Spec.SourceRef.Namespace != "" {
|
||||||
|
rootArgs.namespace = kustomization.Spec.SourceRef.Namespace
|
||||||
|
}
|
||||||
switch kustomization.Spec.SourceRef.Kind {
|
switch kustomization.Spec.SourceRef.Kind {
|
||||||
case sourcev1.GitRepositoryKind:
|
case sourcev1.GitRepositoryKind:
|
||||||
err = reconcileSourceGitCmdRun(nil, []string{kustomization.Spec.SourceRef.Name})
|
err = reconcileCommand{
|
||||||
|
apiType: gitRepositoryType,
|
||||||
|
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
|
||||||
|
}.run(nil, []string{kustomization.Spec.SourceRef.Name})
|
||||||
case sourcev1.BucketKind:
|
case sourcev1.BucketKind:
|
||||||
err = reconcileSourceBucketCmdRun(nil, []string{kustomization.Spec.SourceRef.Name})
|
err = reconcileCommand{
|
||||||
|
apiType: bucketType,
|
||||||
|
object: bucketAdapter{&sourcev1.Bucket{}},
|
||||||
|
}.run(nil, []string{kustomization.Spec.SourceRef.Name})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
rootArgs.namespace = nsCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
lastHandledReconcileAt := kustomization.Status.LastHandledReconcileAt
|
lastHandledReconcileAt := kustomization.Status.LastHandledReconcileAt
|
||||||
|
|||||||
@@ -19,13 +19,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
"k8s.io/client-go/util/retry"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@@ -43,64 +37,16 @@ var reconcileSourceBucketCmd = &cobra.Command{
|
|||||||
Example: ` # Trigger a reconciliation for an existing source
|
Example: ` # Trigger a reconciliation for an existing source
|
||||||
flux reconcile source bucket podinfo
|
flux reconcile source bucket podinfo
|
||||||
`,
|
`,
|
||||||
RunE: reconcileSourceBucketCmdRun,
|
RunE: reconcileCommand{
|
||||||
|
apiType: bucketType,
|
||||||
|
object: bucketAdapter{&sourcev1.Bucket{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
reconcileSourceCmd.AddCommand(reconcileSourceBucketCmd)
|
reconcileSourceCmd.AddCommand(reconcileSourceBucketCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reconcileSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("source name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var bucket sourcev1.Bucket
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &bucket)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if bucket.Spec.Suspend {
|
|
||||||
return fmt.Errorf("resource is suspended")
|
|
||||||
}
|
|
||||||
|
|
||||||
lastHandledReconcileAt := bucket.Status.LastHandledReconcileAt
|
|
||||||
logger.Actionf("annotating Bucket source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
if err := requestBucketReconciliation(ctx, kubeClient, namespacedName, &bucket); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("Bucket source annotated")
|
|
||||||
|
|
||||||
logger.Waitingf("waiting for Bucket source reconciliation")
|
|
||||||
if err := wait.PollImmediate(
|
|
||||||
rootArgs.pollInterval, rootArgs.timeout,
|
|
||||||
bucketReconciliationHandled(ctx, kubeClient, namespacedName, &bucket, lastHandledReconcileAt),
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("Bucket source reconciliation completed")
|
|
||||||
|
|
||||||
if apimeta.IsStatusConditionFalse(bucket.Status.Conditions, meta.ReadyCondition) {
|
|
||||||
return fmt.Errorf("Bucket source reconciliation failed")
|
|
||||||
}
|
|
||||||
logger.Successf("fetched revision %s", bucket.Status.Artifact.Revision)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBucketReady(ctx context.Context, kubeClient client.Client,
|
func isBucketReady(ctx context.Context, kubeClient client.Client,
|
||||||
namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionFunc {
|
namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionFunc {
|
||||||
return func() (bool, error) {
|
return func() (bool, error) {
|
||||||
@@ -126,30 +72,10 @@ func isBucketReady(ctx context.Context, kubeClient client.Client,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func bucketReconciliationHandled(ctx context.Context, kubeClient client.Client,
|
func (obj bucketAdapter) lastHandledReconcileRequest() string {
|
||||||
namespacedName types.NamespacedName, bucket *sourcev1.Bucket, lastHandledReconcileAt string) wait.ConditionFunc {
|
return obj.Status.GetLastHandledReconcileRequest()
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, bucket)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return bucket.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestBucketReconciliation(ctx context.Context, kubeClient client.Client,
|
func (obj bucketAdapter) successMessage() string {
|
||||||
namespacedName types.NamespacedName, bucket *sourcev1.Bucket) error {
|
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
||||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
|
||||||
if err := kubeClient.Get(ctx, namespacedName, bucket); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if bucket.Annotations == nil {
|
|
||||||
bucket.Annotations = map[string]string{
|
|
||||||
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bucket.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
|
|
||||||
}
|
|
||||||
return kubeClient.Update(ctx, bucket)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,21 +17,9 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
"k8s.io/client-go/util/retry"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var reconcileSourceGitCmd = &cobra.Command{
|
var reconcileSourceGitCmd = &cobra.Command{
|
||||||
@@ -41,86 +29,20 @@ var reconcileSourceGitCmd = &cobra.Command{
|
|||||||
Example: ` # Trigger a git pull for an existing source
|
Example: ` # Trigger a git pull for an existing source
|
||||||
flux reconcile source git podinfo
|
flux reconcile source git podinfo
|
||||||
`,
|
`,
|
||||||
RunE: reconcileSourceGitCmdRun,
|
RunE: reconcileCommand{
|
||||||
|
apiType: gitRepositoryType,
|
||||||
|
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
reconcileSourceCmd.AddCommand(reconcileSourceGitCmd)
|
reconcileSourceCmd.AddCommand(reconcileSourceGitCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reconcileSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj gitRepositoryAdapter) lastHandledReconcileRequest() string {
|
||||||
if len(args) < 1 {
|
return obj.Status.GetLastHandledReconcileRequest()
|
||||||
return fmt.Errorf("source name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var repository sourcev1.GitRepository
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &repository)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if repository.Spec.Suspend {
|
|
||||||
return fmt.Errorf("resource is suspended")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("annotating GitRepository source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
if err := requestGitRepositoryReconciliation(ctx, kubeClient, namespacedName, &repository); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("GitRepository source annotated")
|
|
||||||
|
|
||||||
lastHandledReconcileAt := repository.Status.LastHandledReconcileAt
|
|
||||||
logger.Waitingf("waiting for GitRepository source reconciliation")
|
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
|
||||||
gitRepositoryReconciliationHandled(ctx, kubeClient, namespacedName, &repository, lastHandledReconcileAt)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("GitRepository source reconciliation completed")
|
|
||||||
|
|
||||||
if apimeta.IsStatusConditionFalse(repository.Status.Conditions, meta.ReadyCondition) {
|
|
||||||
return fmt.Errorf("GitRepository source reconciliation failed")
|
|
||||||
}
|
|
||||||
logger.Successf("fetched revision %s", repository.Status.Artifact.Revision)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func gitRepositoryReconciliationHandled(ctx context.Context, kubeClient client.Client,
|
func (obj gitRepositoryAdapter) successMessage() string {
|
||||||
namespacedName types.NamespacedName, repository *sourcev1.GitRepository, lastHandledReconcileAt string) wait.ConditionFunc {
|
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, repository)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return repository.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestGitRepositoryReconciliation(ctx context.Context, kubeClient client.Client,
|
|
||||||
namespacedName types.NamespacedName, repository *sourcev1.GitRepository) error {
|
|
||||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
|
||||||
if err := kubeClient.Get(ctx, namespacedName, repository); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if repository.Annotations == nil {
|
|
||||||
repository.Annotations = map[string]string{
|
|
||||||
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
repository.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
|
|
||||||
}
|
|
||||||
return kubeClient.Update(ctx, repository)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,22 +17,9 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
|
||||||
"k8s.io/client-go/util/retry"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var reconcileSourceHelmCmd = &cobra.Command{
|
var reconcileSourceHelmCmd = &cobra.Command{
|
||||||
@@ -42,86 +29,20 @@ var reconcileSourceHelmCmd = &cobra.Command{
|
|||||||
Example: ` # Trigger a reconciliation for an existing source
|
Example: ` # Trigger a reconciliation for an existing source
|
||||||
flux reconcile source helm podinfo
|
flux reconcile source helm podinfo
|
||||||
`,
|
`,
|
||||||
RunE: reconcileSourceHelmCmdRun,
|
RunE: reconcileCommand{
|
||||||
|
apiType: helmRepositoryType,
|
||||||
|
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
reconcileSourceCmd.AddCommand(reconcileSourceHelmCmd)
|
reconcileSourceCmd.AddCommand(reconcileSourceHelmCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reconcileSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj helmRepositoryAdapter) lastHandledReconcileRequest() string {
|
||||||
if len(args) < 1 {
|
return obj.Status.GetLastHandledReconcileRequest()
|
||||||
return fmt.Errorf("HelmRepository source name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var repository sourcev1.HelmRepository
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &repository)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if repository.Spec.Suspend {
|
|
||||||
return fmt.Errorf("resource is suspended")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("annotating HelmRepository source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
if err := requestHelmRepositoryReconciliation(ctx, kubeClient, namespacedName, &repository); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("HelmRepository source annotated")
|
|
||||||
|
|
||||||
lastHandledReconcileAt := repository.Status.LastHandledReconcileAt
|
|
||||||
logger.Waitingf("waiting for HelmRepository source reconciliation")
|
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
|
||||||
helmRepositoryReconciliationHandled(ctx, kubeClient, namespacedName, &repository, lastHandledReconcileAt)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("HelmRepository source reconciliation completed")
|
|
||||||
|
|
||||||
if apimeta.IsStatusConditionFalse(repository.Status.Conditions, meta.ReadyCondition) {
|
|
||||||
return fmt.Errorf("HelmRepository source reconciliation failed")
|
|
||||||
}
|
|
||||||
logger.Successf("fetched revision %s", repository.Status.Artifact.Revision)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func helmRepositoryReconciliationHandled(ctx context.Context, kubeClient client.Client,
|
func (obj helmRepositoryAdapter) successMessage() string {
|
||||||
namespacedName types.NamespacedName, repository *sourcev1.HelmRepository, lastHandledReconcileAt string) wait.ConditionFunc {
|
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, repository)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return repository.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestHelmRepositoryReconciliation(ctx context.Context, kubeClient client.Client,
|
|
||||||
namespacedName types.NamespacedName, repository *sourcev1.HelmRepository) error {
|
|
||||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
|
||||||
if err := kubeClient.Get(ctx, namespacedName, repository); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if repository.Annotations == nil {
|
|
||||||
repository.Annotations = map[string]string{
|
|
||||||
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
repository.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
|
|
||||||
}
|
|
||||||
return kubeClient.Update(ctx, repository)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,20 +17,9 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var resumeHrCmd = &cobra.Command{
|
var resumeHrCmd = &cobra.Command{
|
||||||
@@ -42,76 +31,24 @@ finish the apply.`,
|
|||||||
Example: ` # Resume reconciliation for an existing Helm release
|
Example: ` # Resume reconciliation for an existing Helm release
|
||||||
flux resume hr podinfo
|
flux resume hr podinfo
|
||||||
`,
|
`,
|
||||||
RunE: resumeHrCmdRun,
|
RunE: resumeCommand{
|
||||||
|
apiType: helmReleaseType,
|
||||||
|
object: helmReleaseAdapter{&helmv2.HelmRelease{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
resumeCmd.AddCommand(resumeHrCmd)
|
resumeCmd.AddCommand(resumeHrCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resumeHrCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj helmReleaseAdapter) getObservedGeneration() int64 {
|
||||||
if len(args) < 1 {
|
return obj.HelmRelease.Status.ObservedGeneration
|
||||||
return fmt.Errorf("HelmRelease name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var helmRelease helmv2.HelmRelease
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("resuming HelmRelease %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
helmRelease.Spec.Suspend = false
|
|
||||||
if err := kubeClient.Update(ctx, &helmRelease); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("HelmRelease resumed")
|
|
||||||
|
|
||||||
logger.Waitingf("waiting for HelmRelease reconciliation")
|
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
|
||||||
isHelmReleaseResumed(ctx, kubeClient, namespacedName, &helmRelease)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("HelmRelease reconciliation completed")
|
|
||||||
|
|
||||||
logger.Successf("applied revision %s", helmRelease.Status.LastAppliedRevision)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isHelmReleaseResumed(ctx context.Context, kubeClient client.Client,
|
func (obj helmReleaseAdapter) setUnsuspended() {
|
||||||
namespacedName types.NamespacedName, helmRelease *helmv2.HelmRelease) wait.ConditionFunc {
|
obj.HelmRelease.Spec.Suspend = false
|
||||||
return func() (bool, error) {
|
}
|
||||||
err := kubeClient.Get(ctx, namespacedName, helmRelease)
|
|
||||||
if err != nil {
|
func (obj helmReleaseAdapter) successMessage() string {
|
||||||
return false, err
|
return fmt.Sprintf("applied revision %s", obj.Status.LastAppliedRevision)
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm the state we are observing is for the current generation
|
|
||||||
if helmRelease.Generation != helmRelease.Status.ObservedGeneration {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c := apimeta.FindStatusCondition(helmRelease.Status.Conditions, meta.ReadyCondition); c != nil {
|
|
||||||
switch c.Status {
|
|
||||||
case metav1.ConditionTrue:
|
|
||||||
return true, nil
|
|
||||||
case metav1.ConditionFalse:
|
|
||||||
return false, fmt.Errorf(c.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,19 +17,10 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
|
||||||
|
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var resumeKsCmd = &cobra.Command{
|
var resumeKsCmd = &cobra.Command{
|
||||||
@@ -41,76 +32,24 @@ finish the apply.`,
|
|||||||
Example: ` # Resume reconciliation for an existing Kustomization
|
Example: ` # Resume reconciliation for an existing Kustomization
|
||||||
flux resume ks podinfo
|
flux resume ks podinfo
|
||||||
`,
|
`,
|
||||||
RunE: resumeKsCmdRun,
|
RunE: resumeCommand{
|
||||||
|
apiType: kustomizationType,
|
||||||
|
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
resumeCmd.AddCommand(resumeKsCmd)
|
resumeCmd.AddCommand(resumeKsCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resumeKsCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj kustomizationAdapter) getObservedGeneration() int64 {
|
||||||
if len(args) < 1 {
|
return obj.Kustomization.Status.ObservedGeneration
|
||||||
return fmt.Errorf("Kustomization name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var kustomization kustomizev1.Kustomization
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &kustomization)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("resuming Kustomization %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
kustomization.Spec.Suspend = false
|
|
||||||
if err := kubeClient.Update(ctx, &kustomization); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("Kustomization resumed")
|
|
||||||
|
|
||||||
logger.Waitingf("waiting for Kustomization reconciliation")
|
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
|
||||||
isKustomizationResumed(ctx, kubeClient, namespacedName, &kustomization)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("Kustomization reconciliation completed")
|
|
||||||
|
|
||||||
logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isKustomizationResumed(ctx context.Context, kubeClient client.Client,
|
func (obj kustomizationAdapter) setUnsuspended() {
|
||||||
namespacedName types.NamespacedName, kustomization *kustomizev1.Kustomization) wait.ConditionFunc {
|
obj.Kustomization.Spec.Suspend = false
|
||||||
return func() (bool, error) {
|
}
|
||||||
err := kubeClient.Get(ctx, namespacedName, kustomization)
|
|
||||||
if err != nil {
|
func (obj kustomizationAdapter) successMessage() string {
|
||||||
return false, err
|
return fmt.Sprintf("applied revision %s", obj.Status.LastAppliedRevision)
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm the state we are observing is for the current generation
|
|
||||||
if kustomization.Generation != kustomization.Status.ObservedGeneration {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c := apimeta.FindStatusCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
|
|
||||||
switch c.Status {
|
|
||||||
case metav1.ConditionTrue:
|
|
||||||
return true, nil
|
|
||||||
case metav1.ConditionFalse:
|
|
||||||
return false, fmt.Errorf(c.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,20 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var resumeSourceBucketCmd = &cobra.Command{
|
var resumeSourceBucketCmd = &cobra.Command{
|
||||||
@@ -40,76 +28,20 @@ var resumeSourceBucketCmd = &cobra.Command{
|
|||||||
Example: ` # Resume reconciliation for an existing Bucket
|
Example: ` # Resume reconciliation for an existing Bucket
|
||||||
flux resume source bucket podinfo
|
flux resume source bucket podinfo
|
||||||
`,
|
`,
|
||||||
RunE: resumeSourceBucketCmdRun,
|
RunE: resumeCommand{
|
||||||
|
apiType: bucketType,
|
||||||
|
object: &bucketAdapter{&sourcev1.Bucket{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
resumeSourceCmd.AddCommand(resumeSourceBucketCmd)
|
resumeSourceCmd.AddCommand(resumeSourceBucketCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resumeSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj bucketAdapter) getObservedGeneration() int64 {
|
||||||
if len(args) < 1 {
|
return obj.Bucket.Status.ObservedGeneration
|
||||||
return fmt.Errorf("source name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var bucket sourcev1.Bucket
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &bucket)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("resuming source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
bucket.Spec.Suspend = false
|
|
||||||
if err := kubeClient.Update(ctx, &bucket); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("source resumed")
|
|
||||||
|
|
||||||
logger.Waitingf("waiting for Bucket reconciliation")
|
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
|
||||||
isBucketResumed(ctx, kubeClient, namespacedName, &bucket)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("Bucket reconciliation completed")
|
|
||||||
|
|
||||||
logger.Successf("fetched revision %s", bucket.Status.Artifact.Revision)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isBucketResumed(ctx context.Context, kubeClient client.Client,
|
func (obj bucketAdapter) setUnsuspended() {
|
||||||
namespacedName types.NamespacedName, bucket *sourcev1.Bucket) wait.ConditionFunc {
|
obj.Bucket.Spec.Suspend = false
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, bucket)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm the state we are observing is for the current generation
|
|
||||||
if bucket.Generation != bucket.Status.ObservedGeneration {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c := apimeta.FindStatusCondition(bucket.Status.Conditions, meta.ReadyCondition); c != nil {
|
|
||||||
switch c.Status {
|
|
||||||
case metav1.ConditionTrue:
|
|
||||||
return true, nil
|
|
||||||
case metav1.ConditionFalse:
|
|
||||||
return false, fmt.Errorf(c.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,20 +17,10 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var resumeSourceHelmChartCmd = &cobra.Command{
|
var resumeSourceHelmChartCmd = &cobra.Command{
|
||||||
@@ -40,76 +30,24 @@ var resumeSourceHelmChartCmd = &cobra.Command{
|
|||||||
Example: ` # Resume reconciliation for an existing HelmChart
|
Example: ` # Resume reconciliation for an existing HelmChart
|
||||||
flux resume source chart podinfo
|
flux resume source chart podinfo
|
||||||
`,
|
`,
|
||||||
RunE: resumeSourceHelmChartCmdRun,
|
RunE: resumeCommand{
|
||||||
|
apiType: helmChartType,
|
||||||
|
object: &helmChartAdapter{&sourcev1.HelmChart{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
resumeSourceCmd.AddCommand(resumeSourceHelmChartCmd)
|
resumeSourceCmd.AddCommand(resumeSourceHelmChartCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resumeSourceHelmChartCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj helmChartAdapter) getObservedGeneration() int64 {
|
||||||
if len(args) < 1 {
|
return obj.HelmChart.Status.ObservedGeneration
|
||||||
return fmt.Errorf("source name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var repository sourcev1.HelmChart
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &repository)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("resuming source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
repository.Spec.Suspend = false
|
|
||||||
if err := kubeClient.Update(ctx, &repository); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("source resumed")
|
|
||||||
|
|
||||||
logger.Waitingf("waiting for HelmChart reconciliation")
|
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
|
||||||
isHelmChartResumed(ctx, kubeClient, namespacedName, &repository)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("HelmChart reconciliation completed")
|
|
||||||
|
|
||||||
logger.Successf("fetched revision %s", repository.Status.Artifact.Revision)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isHelmChartResumed(ctx context.Context, kubeClient client.Client,
|
func (obj helmChartAdapter) setUnsuspended() {
|
||||||
namespacedName types.NamespacedName, chart *sourcev1.HelmChart) wait.ConditionFunc {
|
obj.HelmChart.Spec.Suspend = false
|
||||||
return func() (bool, error) {
|
}
|
||||||
err := kubeClient.Get(ctx, namespacedName, chart)
|
|
||||||
if err != nil {
|
func (obj helmChartAdapter) successMessage() string {
|
||||||
return false, err
|
return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm the state we are observing is for the current generation
|
|
||||||
if chart.Generation != chart.Status.ObservedGeneration {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c := apimeta.FindStatusCondition(chart.Status.Conditions, meta.ReadyCondition); c != nil {
|
|
||||||
switch c.Status {
|
|
||||||
case metav1.ConditionTrue:
|
|
||||||
return true, nil
|
|
||||||
case metav1.ConditionFalse:
|
|
||||||
return false, fmt.Errorf(c.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,20 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var resumeSourceGitCmd = &cobra.Command{
|
var resumeSourceGitCmd = &cobra.Command{
|
||||||
@@ -40,76 +28,20 @@ var resumeSourceGitCmd = &cobra.Command{
|
|||||||
Example: ` # Resume reconciliation for an existing GitRepository
|
Example: ` # Resume reconciliation for an existing GitRepository
|
||||||
flux resume source git podinfo
|
flux resume source git podinfo
|
||||||
`,
|
`,
|
||||||
RunE: resumeSourceGitCmdRun,
|
RunE: resumeCommand{
|
||||||
|
apiType: gitRepositoryType,
|
||||||
|
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
resumeSourceCmd.AddCommand(resumeSourceGitCmd)
|
resumeSourceCmd.AddCommand(resumeSourceGitCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resumeSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj gitRepositoryAdapter) getObservedGeneration() int64 {
|
||||||
if len(args) < 1 {
|
return obj.GitRepository.Status.ObservedGeneration
|
||||||
return fmt.Errorf("source name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var repository sourcev1.GitRepository
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &repository)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("resuming source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
repository.Spec.Suspend = false
|
|
||||||
if err := kubeClient.Update(ctx, &repository); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("source resumed")
|
|
||||||
|
|
||||||
logger.Waitingf("waiting for GitRepository reconciliation")
|
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
|
||||||
isGitRepositoryResumed(ctx, kubeClient, namespacedName, &repository)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("GitRepository reconciliation completed")
|
|
||||||
|
|
||||||
logger.Successf("fetched revision %s", repository.Status.Artifact.Revision)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isGitRepositoryResumed(ctx context.Context, kubeClient client.Client,
|
func (obj gitRepositoryAdapter) setUnsuspended() {
|
||||||
namespacedName types.NamespacedName, repository *sourcev1.GitRepository) wait.ConditionFunc {
|
obj.GitRepository.Spec.Suspend = false
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, repository)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm the state we are observing is for the current generation
|
|
||||||
if repository.Generation != repository.Status.ObservedGeneration {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c := apimeta.FindStatusCondition(repository.Status.Conditions, meta.ReadyCondition); c != nil {
|
|
||||||
switch c.Status {
|
|
||||||
case metav1.ConditionTrue:
|
|
||||||
return true, nil
|
|
||||||
case metav1.ConditionFalse:
|
|
||||||
return false, fmt.Errorf(c.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,20 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var resumeSourceHelmCmd = &cobra.Command{
|
var resumeSourceHelmCmd = &cobra.Command{
|
||||||
@@ -40,76 +28,20 @@ var resumeSourceHelmCmd = &cobra.Command{
|
|||||||
Example: ` # Resume reconciliation for an existing HelmRepository
|
Example: ` # Resume reconciliation for an existing HelmRepository
|
||||||
flux resume source helm bitnami
|
flux resume source helm bitnami
|
||||||
`,
|
`,
|
||||||
RunE: resumeSourceHelmCmdRun,
|
RunE: resumeCommand{
|
||||||
|
apiType: helmRepositoryType,
|
||||||
|
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
resumeSourceCmd.AddCommand(resumeSourceHelmCmd)
|
resumeSourceCmd.AddCommand(resumeSourceHelmCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resumeSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj helmRepositoryAdapter) getObservedGeneration() int64 {
|
||||||
if len(args) < 1 {
|
return obj.HelmRepository.Status.ObservedGeneration
|
||||||
return fmt.Errorf("source name is required")
|
|
||||||
}
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var repository sourcev1.HelmRepository
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &repository)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("resuming source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
repository.Spec.Suspend = false
|
|
||||||
if err := kubeClient.Update(ctx, &repository); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("source resumed")
|
|
||||||
|
|
||||||
logger.Waitingf("waiting for HelmRepository reconciliation")
|
|
||||||
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
|
|
||||||
isHelmRepositoryResumed(ctx, kubeClient, namespacedName, &repository)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("HelmRepository reconciliation completed")
|
|
||||||
|
|
||||||
logger.Successf("fetched revision %s", repository.Status.Artifact.Revision)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isHelmRepositoryResumed(ctx context.Context, kubeClient client.Client,
|
func (obj helmRepositoryAdapter) setUnsuspended() {
|
||||||
namespacedName types.NamespacedName, repository *sourcev1.HelmRepository) wait.ConditionFunc {
|
obj.HelmRepository.Spec.Suspend = false
|
||||||
return func() (bool, error) {
|
|
||||||
err := kubeClient.Get(ctx, namespacedName, repository)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm the state we are observing is for the current generation
|
|
||||||
if repository.Generation != repository.Status.ObservedGeneration {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c := apimeta.FindStatusCondition(repository.Status.Conditions, meta.ReadyCondition); c != nil {
|
|
||||||
switch c.Status {
|
|
||||||
case metav1.ConditionTrue:
|
|
||||||
return true, nil
|
|
||||||
case metav1.ConditionFalse:
|
|
||||||
return false, fmt.Errorf(c.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var suspendHrCmd = &cobra.Command{
|
var suspendHrCmd = &cobra.Command{
|
||||||
@@ -35,43 +29,20 @@ var suspendHrCmd = &cobra.Command{
|
|||||||
Example: ` # Suspend reconciliation for an existing Helm release
|
Example: ` # Suspend reconciliation for an existing Helm release
|
||||||
flux suspend hr podinfo
|
flux suspend hr podinfo
|
||||||
`,
|
`,
|
||||||
RunE: suspendHrCmdRun,
|
RunE: suspendCommand{
|
||||||
|
apiType: helmReleaseType,
|
||||||
|
object: &helmReleaseAdapter{&helmv2.HelmRelease{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
suspendCmd.AddCommand(suspendHrCmd)
|
suspendCmd.AddCommand(suspendHrCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func suspendHrCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj helmReleaseAdapter) isSuspended() bool {
|
||||||
if len(args) < 1 {
|
return obj.HelmRelease.Spec.Suspend
|
||||||
return fmt.Errorf("HelmRelease name is required")
|
}
|
||||||
}
|
|
||||||
name := args[0]
|
func (obj helmReleaseAdapter) setSuspended() {
|
||||||
|
obj.HelmRelease.Spec.Suspend = true
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var helmRelease helmv2.HelmRelease
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("suspending HelmRelease %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
helmRelease.Spec.Suspend = true
|
|
||||||
if err := kubeClient.Update(ctx, &helmRelease); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("HelmRelease suspended")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var suspendKsCmd = &cobra.Command{
|
var suspendKsCmd = &cobra.Command{
|
||||||
@@ -34,43 +29,20 @@ var suspendKsCmd = &cobra.Command{
|
|||||||
Example: ` # Suspend reconciliation for an existing Kustomization
|
Example: ` # Suspend reconciliation for an existing Kustomization
|
||||||
flux suspend ks podinfo
|
flux suspend ks podinfo
|
||||||
`,
|
`,
|
||||||
RunE: suspendKsCmdRun,
|
RunE: suspendCommand{
|
||||||
|
apiType: kustomizationType,
|
||||||
|
object: kustomizationAdapter{&kustomizev1.Kustomization{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
suspendCmd.AddCommand(suspendKsCmd)
|
suspendCmd.AddCommand(suspendKsCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func suspendKsCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj kustomizationAdapter) isSuspended() bool {
|
||||||
if len(args) < 1 {
|
return obj.Kustomization.Spec.Suspend
|
||||||
return fmt.Errorf("kustomization name is required")
|
}
|
||||||
}
|
|
||||||
name := args[0]
|
func (obj kustomizationAdapter) setSuspended() {
|
||||||
|
obj.Kustomization.Spec.Suspend = true
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var kustomization kustomizev1.Kustomization
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &kustomization)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("suspending kustomization %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
kustomization.Spec.Suspend = true
|
|
||||||
if err := kubeClient.Update(ctx, &kustomization); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("kustomization suspended")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var suspendSourceBucketCmd = &cobra.Command{
|
var suspendSourceBucketCmd = &cobra.Command{
|
||||||
@@ -33,43 +28,20 @@ var suspendSourceBucketCmd = &cobra.Command{
|
|||||||
Example: ` # Suspend reconciliation for an existing Bucket
|
Example: ` # Suspend reconciliation for an existing Bucket
|
||||||
flux suspend source bucket podinfo
|
flux suspend source bucket podinfo
|
||||||
`,
|
`,
|
||||||
RunE: suspendSourceBucketCmdRun,
|
RunE: suspendCommand{
|
||||||
|
apiType: bucketType,
|
||||||
|
object: bucketAdapter{&sourcev1.Bucket{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
suspendSourceCmd.AddCommand(suspendSourceBucketCmd)
|
suspendSourceCmd.AddCommand(suspendSourceBucketCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func suspendSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj bucketAdapter) isSuspended() bool {
|
||||||
if len(args) < 1 {
|
return obj.Bucket.Spec.Suspend
|
||||||
return fmt.Errorf("source name is required")
|
}
|
||||||
}
|
|
||||||
name := args[0]
|
func (obj bucketAdapter) setSuspended() {
|
||||||
|
obj.Bucket.Spec.Suspend = true
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var bucket sourcev1.Bucket
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &bucket)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("suspending source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
bucket.Spec.Suspend = true
|
|
||||||
if err := kubeClient.Update(ctx, &bucket); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("source suspended")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,9 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var suspendSourceHelmChartCmd = &cobra.Command{
|
var suspendSourceHelmChartCmd = &cobra.Command{
|
||||||
@@ -33,43 +29,20 @@ var suspendSourceHelmChartCmd = &cobra.Command{
|
|||||||
Example: ` # Suspend reconciliation for an existing HelmChart
|
Example: ` # Suspend reconciliation for an existing HelmChart
|
||||||
flux suspend source chart podinfo
|
flux suspend source chart podinfo
|
||||||
`,
|
`,
|
||||||
RunE: suspendSourceHelmChartCmdRun,
|
RunE: suspendCommand{
|
||||||
|
apiType: helmChartType,
|
||||||
|
object: helmChartAdapter{&sourcev1.HelmChart{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
suspendSourceCmd.AddCommand(suspendSourceHelmChartCmd)
|
suspendSourceCmd.AddCommand(suspendSourceHelmChartCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func suspendSourceHelmChartCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj helmChartAdapter) isSuspended() bool {
|
||||||
if len(args) < 1 {
|
return obj.HelmChart.Spec.Suspend
|
||||||
return fmt.Errorf("source name is required")
|
}
|
||||||
}
|
|
||||||
name := args[0]
|
func (obj helmChartAdapter) setSuspended() {
|
||||||
|
obj.HelmChart.Spec.Suspend = true
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var chart sourcev1.HelmChart
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &chart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("suspending source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
chart.Spec.Suspend = true
|
|
||||||
if err := kubeClient.Update(ctx, &chart); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("source suspended")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,9 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var suspendSourceGitCmd = &cobra.Command{
|
var suspendSourceGitCmd = &cobra.Command{
|
||||||
@@ -33,43 +29,20 @@ var suspendSourceGitCmd = &cobra.Command{
|
|||||||
Example: ` # Suspend reconciliation for an existing GitRepository
|
Example: ` # Suspend reconciliation for an existing GitRepository
|
||||||
flux suspend source git podinfo
|
flux suspend source git podinfo
|
||||||
`,
|
`,
|
||||||
RunE: suspendSourceGitCmdRun,
|
RunE: suspendCommand{
|
||||||
|
apiType: gitRepositoryType,
|
||||||
|
object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
suspendSourceCmd.AddCommand(suspendSourceGitCmd)
|
suspendSourceCmd.AddCommand(suspendSourceGitCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func suspendSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj gitRepositoryAdapter) isSuspended() bool {
|
||||||
if len(args) < 1 {
|
return obj.GitRepository.Spec.Suspend
|
||||||
return fmt.Errorf("source name is required")
|
}
|
||||||
}
|
|
||||||
name := args[0]
|
func (obj gitRepositoryAdapter) setSuspended() {
|
||||||
|
obj.GitRepository.Spec.Suspend = true
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var repository sourcev1.GitRepository
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &repository)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("suspending source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
repository.Spec.Suspend = true
|
|
||||||
if err := kubeClient.Update(ctx, &repository); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("source suspended")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,9 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var suspendSourceHelmCmd = &cobra.Command{
|
var suspendSourceHelmCmd = &cobra.Command{
|
||||||
@@ -33,43 +29,20 @@ var suspendSourceHelmCmd = &cobra.Command{
|
|||||||
Example: ` # Suspend reconciliation for an existing HelmRepository
|
Example: ` # Suspend reconciliation for an existing HelmRepository
|
||||||
flux suspend source helm bitnami
|
flux suspend source helm bitnami
|
||||||
`,
|
`,
|
||||||
RunE: suspendSourceHelmCmdRun,
|
RunE: suspendCommand{
|
||||||
|
apiType: helmRepositoryType,
|
||||||
|
object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
suspendSourceCmd.AddCommand(suspendSourceHelmCmd)
|
suspendSourceCmd.AddCommand(suspendSourceHelmCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func suspendSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
func (obj helmRepositoryAdapter) isSuspended() bool {
|
||||||
if len(args) < 1 {
|
return obj.HelmRepository.Spec.Suspend
|
||||||
return fmt.Errorf("source name is required")
|
}
|
||||||
}
|
|
||||||
name := args[0]
|
func (obj helmRepositoryAdapter) setSuspended() {
|
||||||
|
obj.HelmRepository.Spec.Suspend = true
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacedName := types.NamespacedName{
|
|
||||||
Namespace: rootArgs.namespace,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
var repository sourcev1.HelmRepository
|
|
||||||
err = kubeClient.Get(ctx, namespacedName, &repository)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Actionf("suspending source %s in %s namespace", name, rootArgs.namespace)
|
|
||||||
repository.Spec.Suspend = true
|
|
||||||
if err := kubeClient.Update(ctx, &repository); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Successf("source suspended")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,98 +1,5 @@
|
|||||||
# Frequently asked questions
|
# Frequently asked questions
|
||||||
|
|
||||||
## General questions
|
|
||||||
|
|
||||||
### What does Flux v2 mean for Flux?
|
|
||||||
|
|
||||||
Flux v1 is a monolithic do-it-all operator; Flux v2 separates the functionalities into specialized controllers, collectively called the GitOps Toolkit.
|
|
||||||
|
|
||||||
You can install and operate Flux v2 simply using the `flux` command. You can easily pick and choose the functionality you need and extend it to serve your own purposes.
|
|
||||||
|
|
||||||
The timeline we are looking at right now is:
|
|
||||||
|
|
||||||
1. Put Flux v1 into maintenance mode (no new features being added; bugfixes and CVEs patched only).
|
|
||||||
1. Continue work on the [Flux v2 roadmap](https://toolkit.fluxcd.io/roadmap/).
|
|
||||||
1. We will provide transition guides for specific user groups, e.g. users of Flux v1 in read-only mode, or of Helm Operator v1, etc. once the functionality is integrated into Flux v2 and it's deemed "ready".
|
|
||||||
1. Once the use-cases of Flux v1 are covered, we will continue supporting Flux v1 for 6 months. This will be the transition period before it's considered unsupported.
|
|
||||||
|
|
||||||
### Why did you rewrite Flux?
|
|
||||||
|
|
||||||
Flux v2 implements its functionality in individual controllers, which allowed us to address long-standing feature requests much more easily.
|
|
||||||
|
|
||||||
By basing these controllers on modern Kubernetes tooling (`controller-runtime` libraries), they can be dynamically configured with Kubernetes custom resources either by cluster admins or by other automated tools -- and you get greatly increased observability.
|
|
||||||
|
|
||||||
This gave us the opportunity to build Flux v2 with the top Flux v1 feature requests in mind:
|
|
||||||
|
|
||||||
- Supporting multiple source Git repositories
|
|
||||||
- Operational insight through health checks, events and alerts
|
|
||||||
- Multi-tenancy capabilities, like applying each source repository with its own set of permissions
|
|
||||||
|
|
||||||
On top of that, testing the individual components and understanding the codebase becomes a lot easier.
|
|
||||||
|
|
||||||
### What are significant new differences between Flux v1 and Flux v2?
|
|
||||||
|
|
||||||
#### Reconciliation
|
|
||||||
|
|
||||||
Flux v1 | Flux v2
|
|
||||||
---------------------------------- | ----------------------------------
|
|
||||||
Limited to a single Git repository | Multiple Git repositories
|
|
||||||
Declarative config via arguments in the Flux deployment | `GitRepository` custom resource, which produces an artifact which can be reconciled by other controllers
|
|
||||||
Follow `HEAD` of Git branches | Supports Git branches, pinning on commits and tags, follow SemVer tag ranges
|
|
||||||
Suspending of reconciliation by downscaling Flux deployment | Reconciliation can be paused per resource by suspending the `GitRepository`
|
|
||||||
Credentials config via Arguments and/or Secret volume mounts in the Flux pod | Credentials config per `GitRepository` resource: SSH private key, HTTP/S username/password/token, OpenPGP public keys
|
|
||||||
|
|
||||||
#### `kustomize` support
|
|
||||||
|
|
||||||
Flux v1 | Flux v2
|
|
||||||
---------------------------------- | ----------------------------------
|
|
||||||
Declarative config through `.flux.yaml` files in the Git repository | Declarative config through a `Kustomization` custom resource, consuming the artifact from the GitRepository
|
|
||||||
Manifests are generated via shell exec and then reconciled by `fluxd` | Generation, server-side validation, and reconciliation is handled by a specialised `kustomize-controller`
|
|
||||||
Reconciliation using the service account of the Flux deployment | Support for service account impersonation
|
|
||||||
Garbage collection needs cluster role binding for Flux to query the Kubernetes discovery API | Garbage collection needs no cluster role binding or access to Kubernetes discovery API
|
|
||||||
Support for custom commands and generators executed by fluxd in a POSIX shell | No support for custom commands
|
|
||||||
|
|
||||||
#### Helm integration
|
|
||||||
|
|
||||||
Flux v1 | Flux v2
|
|
||||||
---------------------------------- | ----------------------------------
|
|
||||||
Declarative config in a single Helm custom resource | Declarative config through `HelmRepository`, `GitRepository`, `Bucket`, `HelmChart` and `HelmRelease` custom resources
|
|
||||||
Chart synchronisation embedded in the operator | Extensive release configuration options, and a reconciliation interval per source
|
|
||||||
Support for fixed SemVer versions from Helm repositories | Support for SemVer ranges for `HelmChart` resources
|
|
||||||
Git repository synchronisation on a global interval | Planned support for charts from GitRepository sources
|
|
||||||
Limited observability via the status object of the HelmRelease resource | Better observability via the HelmRelease status object, Kubernetes events, and notifications
|
|
||||||
Resource heavy, relatively slow | Better performance
|
|
||||||
Chart changes from Git sources are determined from Git metadata | Chart changes must be accompanied by a version bump in `Chart.yaml` to produce a new artifact
|
|
||||||
|
|
||||||
#### Notifications, webhooks, observability
|
|
||||||
|
|
||||||
Flux v1 | Flux v2
|
|
||||||
---------------------------------- | ----------------------------------
|
|
||||||
Emits "custom Flux events" to a webhook endpoint | Emits Kubernetes events for included custom resources
|
|
||||||
RPC endpoint can be configured to a 3rd party solution like FluxCloud to be forwarded as notifications to e.g. Slack | Flux v2 components can be configured to POST the events to a `notification-controller` endpoint. Selective forwarding of POSTed events as notifications using `Provider` and `Alert` custom resources.
|
|
||||||
Webhook receiver is a side-project | Webhook receiver, handling a wide range of platforms, is included
|
|
||||||
Unstructured logging | Structured logging for all components
|
|
||||||
Custom Prometheus metrics | Generic / common `controller-runtime` Prometheus metrics
|
|
||||||
|
|
||||||
### How can I get involved?
|
|
||||||
|
|
||||||
There are a variety of ways and we look forward to having you on board building the future of GitOps together:
|
|
||||||
|
|
||||||
- [Discuss the direction](https://github.com/fluxcd/flux2/discussions) of Flux v2 with us
|
|
||||||
- Join us in #flux-dev on the [CNCF Slack](https://slack.cncf.io)
|
|
||||||
- Check out our [contributor docs](https://toolkit.fluxcd.io/contributing/)
|
|
||||||
- Take a look at the [roadmap for Flux v2](https://toolkit.fluxcd.io/roadmap/)
|
|
||||||
|
|
||||||
### Are there any breaking changes?
|
|
||||||
|
|
||||||
- In Flux v1 Kustomize support was implemented through `.flux.yaml` files in the Git repository. As indicated in the comparison table above, while this approach worked, we found it to be error-prone and hard to debug. The new [Kustomization CR](https://github.com/fluxcd/kustomize-controller/blob/master/docs/spec/v1alpha1/kustomization.md) should make troubleshooting much easier. Unfortunately we needed to drop the support for custom commands as running arbitrary shell scripts in-cluster poses serious security concerns.
|
|
||||||
- Helm users: we redesigned the `HelmRelease` API and the automation will work quite differently, so upgrading to `HelmRelease` v2 will require a little work from you, but you will gain more flexibility, better observability and performance.
|
|
||||||
|
|
||||||
### Is the GitOps Toolkit related to the GitOps Engine?
|
|
||||||
|
|
||||||
In an announcement in August 2019, the expectation was set that the Flux project would integrate the GitOps Engine, then being factored out of ArgoCD. Since the result would be backward-incompatible, it would require a major version bump: Flux v2.
|
|
||||||
|
|
||||||
After experimentation and considerable thought, we (the maintainers) have found a path to Flux v2 that we think better serves our vision of GitOps: the GitOps Toolkit. In consequence, we do not now plan to integrate GitOps Engine into Flux.
|
|
||||||
|
|
||||||
## Kustomize questions
|
## Kustomize questions
|
||||||
|
|
||||||
### Are there two Kustomization types?
|
### Are there two Kustomization types?
|
||||||
@@ -242,3 +149,127 @@ The kustomize-controller creates `kustomization.yaml` files similar to:
|
|||||||
```sh
|
```sh
|
||||||
cd ./deploy/prod && kustomize create --autodetect --recursive
|
cd ./deploy/prod && kustomize create --autodetect --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Helm questions
|
||||||
|
|
||||||
|
### How to debug "not ready" errors?
|
||||||
|
|
||||||
|
Misconfiguring the `HelmRelease.spec.chart`, like a typo in the chart name, version or chart source URL
|
||||||
|
would result in a "HelmChart is not ready" error displayed by:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ flux get helmreleases --all-namespaces
|
||||||
|
NAMESPACE NAME READY MESSAGE
|
||||||
|
default podinfo False HelmChart 'default/default-podinfo' is not ready
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to get to the root cause, first make sure the source e.g. the `HelmRepository`
|
||||||
|
is configured properly and has access to the remote `index.yaml`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ flux get sources helm --all-namespaces
|
||||||
|
NAMESPACE NAME READY MESSAGE
|
||||||
|
default podinfo False failed to fetch https://stefanprodan.github.io/podinfo2/index.yaml : 404 Not Found
|
||||||
|
```
|
||||||
|
|
||||||
|
If the source is `Ready`, then the error must be caused by the chart,
|
||||||
|
for example due to an invalid chart name or non-existing version:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ flux get sources chart --all-namespaces
|
||||||
|
NAMESPACE NAME READY MESSAGE
|
||||||
|
default default-podinfo False no chart version found for podinfo-9.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Flux v1 vs v2 questions
|
||||||
|
|
||||||
|
### What does Flux v2 mean for Flux?
|
||||||
|
|
||||||
|
Flux v1 is a monolithic do-it-all operator; Flux v2 separates the functionalities into specialized controllers, collectively called the GitOps Toolkit.
|
||||||
|
|
||||||
|
You can install and operate Flux v2 simply using the `flux` command. You can easily pick and choose the functionality you need and extend it to serve your own purposes.
|
||||||
|
|
||||||
|
The timeline we are looking at right now is:
|
||||||
|
|
||||||
|
1. Put Flux v1 into maintenance mode (no new features being added; bugfixes and CVEs patched only).
|
||||||
|
1. Continue work on the [Flux v2 roadmap](https://toolkit.fluxcd.io/roadmap/).
|
||||||
|
1. We will provide transition guides for specific user groups, e.g. users of Flux v1 in read-only mode, or of Helm Operator v1, etc. once the functionality is integrated into Flux v2 and it's deemed "ready".
|
||||||
|
1. Once the use-cases of Flux v1 are covered, we will continue supporting Flux v1 for 6 months. This will be the transition period before it's considered unsupported.
|
||||||
|
|
||||||
|
### Why did you rewrite Flux?
|
||||||
|
|
||||||
|
Flux v2 implements its functionality in individual controllers, which allowed us to address long-standing feature requests much more easily.
|
||||||
|
|
||||||
|
By basing these controllers on modern Kubernetes tooling (`controller-runtime` libraries), they can be dynamically configured with Kubernetes custom resources either by cluster admins or by other automated tools -- and you get greatly increased observability.
|
||||||
|
|
||||||
|
This gave us the opportunity to build Flux v2 with the top Flux v1 feature requests in mind:
|
||||||
|
|
||||||
|
- Supporting multiple source Git repositories
|
||||||
|
- Operational insight through health checks, events and alerts
|
||||||
|
- Multi-tenancy capabilities, like applying each source repository with its own set of permissions
|
||||||
|
|
||||||
|
On top of that, testing the individual components and understanding the codebase becomes a lot easier.
|
||||||
|
|
||||||
|
### What are significant new differences between Flux v1 and Flux v2?
|
||||||
|
|
||||||
|
#### Reconciliation
|
||||||
|
|
||||||
|
Flux v1 | Flux v2
|
||||||
|
---------------------------------- | ----------------------------------
|
||||||
|
Limited to a single Git repository | Multiple Git repositories
|
||||||
|
Declarative config via arguments in the Flux deployment | `GitRepository` custom resource, which produces an artifact which can be reconciled by other controllers
|
||||||
|
Follow `HEAD` of Git branches | Supports Git branches, pinning on commits and tags, follow SemVer tag ranges
|
||||||
|
Suspending of reconciliation by downscaling Flux deployment | Reconciliation can be paused per resource by suspending the `GitRepository`
|
||||||
|
Credentials config via Arguments and/or Secret volume mounts in the Flux pod | Credentials config per `GitRepository` resource: SSH private key, HTTP/S username/password/token, OpenPGP public keys
|
||||||
|
|
||||||
|
#### `kustomize` support
|
||||||
|
|
||||||
|
Flux v1 | Flux v2
|
||||||
|
---------------------------------- | ----------------------------------
|
||||||
|
Declarative config through `.flux.yaml` files in the Git repository | Declarative config through a `Kustomization` custom resource, consuming the artifact from the GitRepository
|
||||||
|
Manifests are generated via shell exec and then reconciled by `fluxd` | Generation, server-side validation, and reconciliation is handled by a specialised `kustomize-controller`
|
||||||
|
Reconciliation using the service account of the Flux deployment | Support for service account impersonation
|
||||||
|
Garbage collection needs cluster role binding for Flux to query the Kubernetes discovery API | Garbage collection needs no cluster role binding or access to Kubernetes discovery API
|
||||||
|
Support for custom commands and generators executed by fluxd in a POSIX shell | No support for custom commands
|
||||||
|
|
||||||
|
#### Helm integration
|
||||||
|
|
||||||
|
Flux v1 | Flux v2
|
||||||
|
---------------------------------- | ----------------------------------
|
||||||
|
Declarative config in a single Helm custom resource | Declarative config through `HelmRepository`, `GitRepository`, `Bucket`, `HelmChart` and `HelmRelease` custom resources
|
||||||
|
Chart synchronisation embedded in the operator | Extensive release configuration options, and a reconciliation interval per source
|
||||||
|
Support for fixed SemVer versions from Helm repositories | Support for SemVer ranges for `HelmChart` resources
|
||||||
|
Git repository synchronisation on a global interval | Planned support for charts from GitRepository sources
|
||||||
|
Limited observability via the status object of the HelmRelease resource | Better observability via the HelmRelease status object, Kubernetes events, and notifications
|
||||||
|
Resource heavy, relatively slow | Better performance
|
||||||
|
Chart changes from Git sources are determined from Git metadata | Chart changes must be accompanied by a version bump in `Chart.yaml` to produce a new artifact
|
||||||
|
|
||||||
|
#### Notifications, webhooks, observability
|
||||||
|
|
||||||
|
Flux v1 | Flux v2
|
||||||
|
---------------------------------- | ----------------------------------
|
||||||
|
Emits "custom Flux events" to a webhook endpoint | Emits Kubernetes events for included custom resources
|
||||||
|
RPC endpoint can be configured to a 3rd party solution like FluxCloud to be forwarded as notifications to e.g. Slack | Flux v2 components can be configured to POST the events to a `notification-controller` endpoint. Selective forwarding of POSTed events as notifications using `Provider` and `Alert` custom resources.
|
||||||
|
Webhook receiver is a side-project | Webhook receiver, handling a wide range of platforms, is included
|
||||||
|
Unstructured logging | Structured logging for all components
|
||||||
|
Custom Prometheus metrics | Generic / common `controller-runtime` Prometheus metrics
|
||||||
|
|
||||||
|
### How can I get involved?
|
||||||
|
|
||||||
|
There are a variety of ways and we look forward to having you on board building the future of GitOps together:
|
||||||
|
|
||||||
|
- [Discuss the direction](https://github.com/fluxcd/flux2/discussions) of Flux v2 with us
|
||||||
|
- Join us in #flux-dev on the [CNCF Slack](https://slack.cncf.io)
|
||||||
|
- Check out our [contributor docs](https://toolkit.fluxcd.io/contributing/)
|
||||||
|
- Take a look at the [roadmap for Flux v2](https://toolkit.fluxcd.io/roadmap/)
|
||||||
|
|
||||||
|
### Are there any breaking changes?
|
||||||
|
|
||||||
|
- In Flux v1 Kustomize support was implemented through `.flux.yaml` files in the Git repository. As indicated in the comparison table above, while this approach worked, we found it to be error-prone and hard to debug. The new [Kustomization CR](https://github.com/fluxcd/kustomize-controller/blob/master/docs/spec/v1alpha1/kustomization.md) should make troubleshooting much easier. Unfortunately we needed to drop the support for custom commands as running arbitrary shell scripts in-cluster poses serious security concerns.
|
||||||
|
- Helm users: we redesigned the `HelmRelease` API and the automation will work quite differently, so upgrading to `HelmRelease` v2 will require a little work from you, but you will gain more flexibility, better observability and performance.
|
||||||
|
|
||||||
|
### Is the GitOps Toolkit related to the GitOps Engine?
|
||||||
|
|
||||||
|
In an announcement in August 2019, the expectation was set that the Flux project would integrate the GitOps Engine, then being factored out of ArgoCD. Since the result would be backward-incompatible, it would require a major version bump: Flux v2.
|
||||||
|
|
||||||
|
After experimentation and considerable thought, we (the maintainers) have found a path to Flux v2 that we think better serves our vision of GitOps: the GitOps Toolkit. In consequence, we do not now plan to integrate GitOps Engine into Flux.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ This guide walks you through configuring container image scanning and deployment
|
|||||||
For a container image you can configure Flux to:
|
For a container image you can configure Flux to:
|
||||||
|
|
||||||
- scan the container registry and fetch the image tags
|
- scan the container registry and fetch the image tags
|
||||||
- select the latest tag based on a semver range
|
- select the latest tag based on the defined policy (semver, calver, regex)
|
||||||
- replace the tag in Kubernetes manifests (YAML format)
|
- replace the tag in Kubernetes manifests (YAML format)
|
||||||
- checkout a branch, commit and push the changes to the remote Git repository
|
- checkout a branch, commit and push the changes to the remote Git repository
|
||||||
- apply the changes in-cluster and rollout the container image
|
- apply the changes in-cluster and rollout the container image
|
||||||
@@ -17,10 +17,7 @@ For a container image you can configure Flux to:
|
|||||||
For production environments, this feature allows you to automatically deploy application patches
|
For production environments, this feature allows you to automatically deploy application patches
|
||||||
(CVEs and bug fixes), and keep a record of all deployments in Git history.
|
(CVEs and bug fixes), and keep a record of all deployments in Git history.
|
||||||
|
|
||||||
For staging environments, this features allow you to deploy the latest prerelease of an application,
|
**Production CI/CD workflow**
|
||||||
without having to manually edit its deployment manifests in Git.
|
|
||||||
|
|
||||||
Production CI/CD workflow:
|
|
||||||
|
|
||||||
* DEV: push a bug fix to the app repository
|
* DEV: push a bug fix to the app repository
|
||||||
* DEV: bump the patch version and release e.g. `v1.0.1`
|
* DEV: bump the patch version and release e.g. `v1.0.1`
|
||||||
@@ -29,6 +26,17 @@ Production CI/CD workflow:
|
|||||||
* CD: update the image tag in the app manifest to `v1.0.1` (Flux cluster to Git reconciliation)
|
* CD: update the image tag in the app manifest to `v1.0.1` (Flux cluster to Git reconciliation)
|
||||||
* CD: deploy `v1.0.1` to production clusters (Flux Git to cluster reconciliation)
|
* CD: deploy `v1.0.1` to production clusters (Flux Git to cluster reconciliation)
|
||||||
|
|
||||||
|
For staging environments, this features allow you to deploy the latest build of a branch,
|
||||||
|
without having to manually edit the app deployment manifest in Git.
|
||||||
|
|
||||||
|
**Staging CI/CD workflow**
|
||||||
|
|
||||||
|
* DEV: push code changes to the app repository `main` branch
|
||||||
|
* CI: build and push a container image tagged as `${GIT_BRANCH}-${GIT_SHA:0:7}-$(date +%s)`
|
||||||
|
* CD: pull the latest image metadata from the app registry (Flux image scanning)
|
||||||
|
* CD: update the image tag in the app manifest to `main-2d3fcbd-1611906956` (Flux cluster to Git reconciliation)
|
||||||
|
* CD: deploy `main-2d3fcbd-1611906956` to staging clusters (Flux Git to cluster reconciliation)
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
You will need a Kubernetes cluster version 1.16 or newer and kubectl version 1.18.
|
You will need a Kubernetes cluster version 1.16 or newer and kubectl version 1.18.
|
||||||
@@ -260,7 +268,7 @@ spec:
|
|||||||
messageTemplate: '[ci skip] update image'
|
messageTemplate: '[ci skip] update image'
|
||||||
interval: 1m0s
|
interval: 1m0s
|
||||||
update:
|
update:
|
||||||
setters: {}
|
strategy: Setters
|
||||||
```
|
```
|
||||||
|
|
||||||
Commit and push changes to main branch:
|
Commit and push changes to main branch:
|
||||||
@@ -362,6 +370,60 @@ images:
|
|||||||
newTag: 5.0.0 # {"$imagepolicy": "flux-system:podinfo:tag"}
|
newTag: 5.0.0 # {"$imagepolicy": "flux-system:podinfo:tag"}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Trigger image updates with webhooks
|
||||||
|
|
||||||
|
You may want to trigger a deployment
|
||||||
|
as soon as a new image tag is pushed to your container registry.
|
||||||
|
In order to notify the image-reflector-controller about new images,
|
||||||
|
you can [setup webhook receivers](webhook-receivers.md).
|
||||||
|
|
||||||
|
First generate a random string and create a secret with a `token` field:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
TOKEN=$(head -c 12 /dev/urandom | shasum | cut -d ' ' -f1)
|
||||||
|
echo $TOKEN
|
||||||
|
|
||||||
|
kubectl -n flux-system create secret generic webhook-token \
|
||||||
|
--from-literal=token=$TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
Define a receiver for DockerHub:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: notification.toolkit.fluxcd.io/v1beta1
|
||||||
|
kind: Receiver
|
||||||
|
metadata:
|
||||||
|
name: podinfo
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
type: dockerhub
|
||||||
|
secretRef:
|
||||||
|
name: webhook-token
|
||||||
|
resources:
|
||||||
|
- kind: ImageRepository
|
||||||
|
name: podinfo
|
||||||
|
```
|
||||||
|
|
||||||
|
The notification-controller generates a unique URL using the provided token and the receiver name/namespace.
|
||||||
|
|
||||||
|
Find the URL with:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ kubectl -n flux-system get receiver/podinfo
|
||||||
|
|
||||||
|
NAME READY STATUS
|
||||||
|
podinfo True Receiver initialised with URL: /hook/bed6d00b5555b1603e1f59b94d7fdbca58089cb5663633fb83f2815dc626d92b
|
||||||
|
```
|
||||||
|
|
||||||
|
Log in to DockerHub web interface, go to your image registry Settings and select Webhooks.
|
||||||
|
Fill the form "Webhook URL" by composing the address using the receiver
|
||||||
|
LB and the generated URL `http://<LoadBalancerAddress>/<ReceiverURL>`.
|
||||||
|
|
||||||
|
!!! hint "Note"
|
||||||
|
Besides DockerHub, you can define receivers for **Harbor**, **Quay**, **Nexus**, **GCR**,
|
||||||
|
and any other system that supports webhooks e.g. GitHub Actions, Jenkins, CircleCI, etc.
|
||||||
|
See the [Receiver CRD docs](../components/notification/receiver.md) for more details.
|
||||||
|
|
||||||
## ImageRepository cloud providers authentication
|
## ImageRepository cloud providers authentication
|
||||||
|
|
||||||
If relying on a cloud provider image repository, you might need to do some extra
|
If relying on a cloud provider image repository, you might need to do some extra
|
||||||
@@ -501,10 +563,11 @@ $ kubectl create job --from=cronjob/ecr-credentials-sync -n flux-system ecr-cred
|
|||||||
#### Using access token [short-lived]
|
#### Using access token [short-lived]
|
||||||
|
|
||||||
!!!note "Workload Identity"
|
!!!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.
|
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)
|
Take a look at [this guide](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)
|
||||||
|
|
||||||
|
|
||||||
The access token for GCR expires hourly.
|
The access token for GCR expires hourly.
|
||||||
Considering this limitation, one needs to ensure the credentials are being
|
Considering this limitation, one needs to ensure the credentials are being
|
||||||
refreshed before expiration so that the controller can rely on them for
|
refreshed before expiration so that the controller can rely on them for
|
||||||
@@ -598,14 +661,23 @@ $ kubectl create job --from=cronjob/gcr-credentials-sync -n flux-system gcr-cred
|
|||||||
|
|
||||||
!!! warning "Less secure option"
|
!!! warning "Less secure option"
|
||||||
From [Google documentation on authenticating container registry](https://cloud.google.com/container-registry/docs/advanced-authentication#json-key)
|
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.
|
> 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.
|
||||||
|
|
||||||
|
A Json key doesn't expire, so we don't need a cronjob,
|
||||||
|
we just need to create the secret and reference it in the ImagePolicy.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
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 <secret-name> \
|
kubectl create secret docker-registry <secret-name> \
|
||||||
--docker-server=<GCR-REGISTRY> \ # e.g gcr.io
|
--docker-server=<GCR-REGISTRY> \ # e.g gcr.io
|
||||||
@@ -615,4 +687,4 @@ Then create a secret, encrypt it using [Mozilla SOPS](mozilla-sops.md) or [Seale
|
|||||||
|
|
||||||
### Azure Container Registry
|
### Azure Container Registry
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ the API.
|
|||||||
After installing the `flux` CLI and running a couple of very simple commands,
|
After installing the `flux` CLI and running a couple of very simple commands,
|
||||||
you will have a GitOps workflow setup which involves a staging and a production cluster.
|
you will have a GitOps workflow setup which involves a staging and a production cluster.
|
||||||
|
|
||||||
|
If you should need help, please refer to our **[Support page](https://fluxcd.io/support/)**.
|
||||||
|
|
||||||
## More detail on what's in Flux
|
## More detail on what's in Flux
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|||||||
Reference in New Issue
Block a user