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

Compare commits

..

199 Commits

Author SHA1 Message Date
Stefan Prodan
22df860eca Merge pull request #849 from fluxcd/update-components
Update toolkit components
2021-02-02 18:07:29 +02:00
fluxcdbot
f395044d65 Update toolkit components 2021-02-02 15:40:37 +00:00
Hidde Beydals
afe0ddcd84 Merge pull request #824 from fluxcd/upgrade-semver-tip
Highlight PATCH versions can be used to upgrade
2021-02-01 18:41:34 +01:00
Hidde Beydals
2c0323684c Highlight PATCH versions can be used to upgrade
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-02-01 18:21:54 +01:00
Hidde Beydals
6d5ffdea57 Merge pull request #841 from fluxcd/update-components
Update toolkit components
2021-02-01 18:07:15 +01:00
fluxcdbot
648af6e645 Update toolkit components 2021-02-01 16:50:07 +00:00
Hidde Beydals
e1895a4e21 Merge pull request #840 from relu/fix-image-update-docs-ecr-cronjob
Improve image update CronJob examples
2021-02-01 17:49:31 +01:00
Aurel Canciu
d5f45800ae Clarify how to use the generated cronjob secret
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2021-02-01 17:11:14 +01:00
Aurel Canciu
51f9d249ff Fix image update guide ECR cronjob manifest
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2021-02-01 17:11:14 +01:00
Hidde Beydals
6f525356cb Merge pull request #837 from aholbreich/patch-1 2021-02-01 17:10:54 +01:00
Alexander Holbreich
5008f9064e Update image-update.md
Increase attention to missing flux components.

Signed-off-by: Alexander Holbreich <alexander@holbreich.org>
2021-02-01 16:53:19 +01:00
Stefan Prodan
cff96ed7ca Merge pull request #834 from fluxcd/fix-secret-cmd
Fix create secret commands
2021-01-30 16:36:44 +02:00
Stefan Prodan
4e8a600f34 Add e2e tests for create secret commands
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-30 15:19:45 +02:00
Stefan Prodan
4fd5684277 Fix create secret commands
Regression bug introduced in https://github.com/fluxcd/flux2/pull/788

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-30 15:07:48 +02:00
Hidde Beydals
06bf469ba7 Merge pull request #825 from SomtochiAma/refactor-reconcile-command
Refactor resume command
2021-01-29 19:52:07 +01:00
Somtochi Onyekwere
b8a215230c refactor resume command
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-29 19:05:38 +01:00
Stefan Prodan
2460cfcf1c Merge pull request #821 from fluxcd/docs-helm-faq
Add HelmChart not ready to FAQ
2021-01-29 17:05:39 +02:00
Stefan Prodan
364242c857 Add HelmChart not ready to FAQ
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-29 16:20:35 +02:00
Stefan Prodan
29e2900f59 Merge pull request #818 from fluxcd/docs-image-webhook
Add webhook section to image update docs
2021-01-29 13:33:17 +02:00
Stefan Prodan
61e1fb770e Add webhook section to image update docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-29 13:09:01 +02:00
Stefan Prodan
2d3fcbdea3 Merge pull request #815 from fluxcd/e2e-kube-1.20.2
Update e2e tests to Kubernetes v1.20.2
2021-01-29 09:49:42 +02:00
Stefan Prodan
47e15cee3d Update e2e tests to Kubernetes v1.20.2
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-28 19:50:19 +02:00
Stefan Prodan
adeb3e3f42 Merge pull request #814 from mewzherder/patch-9
Community section clarity of purpose + support page link
2021-01-28 19:42:42 +02:00
mewzherder
fb1278285b Community section clarity of purpose + support page link
Signed-off-by: mewzherder <tamao@weave.works>
2021-01-28 09:24:08 -08:00
Stefan Prodan
e371610849 Merge pull request #812 from chanwit/network_policy_e2e
Enable network policy in e2e
2021-01-28 16:46:45 +02:00
Chanwit Kaewkasi
424de63bd1 update KIND to v0.10.0 and node to 1.16.15
Signed-off-by: Chanwit Kaewkasi <chanwit@gmail.com>
2021-01-28 21:21:02 +07:00
Chanwit Kaewkasi
832c925d39 setup Calico to enable network policy for e2e testing
Signed-off-by: Chanwit Kaewkasi <chanwit@gmail.com>
2021-01-28 21:21:02 +07:00
Chanwit Kaewkasi
378f118d51 add kind config to disable kind-net
Signed-off-by: Chanwit Kaewkasi <chanwit@gmail.com>
2021-01-28 21:21:02 +07:00
Hidde Beydals
d651777122 Merge pull request #813 from fluxcd/iua-docs-example-update
Update docs ImageUpdateAutomation example
2021-01-28 15:13:40 +01:00
Aurel Canciu
65d8ebabb8 Update docs ImageUpdateAutomation example
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2021-01-28 15:57:25 +02:00
Hidde Beydals
9195ed9a1b Merge pull request #809 from SomtochiAma/refactor-reconcile-command
Refactor reconcile commands
2021-01-28 14:16:15 +01:00
Somtochi Onyekwere
5df8f7313c Refactor reconcile commands
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-28 13:21:29 +01:00
Hidde Beydals
25ed6ca0a4 Merge pull request #780 from dholbach/link-to-support-page 2021-01-28 11:59:08 +01:00
Daniel Holbach
9f972995bd add very basic issue template
Also link to support page.

	Fixes: fluxcd/website#77

Signed-off-by: Daniel Holbach <daniel@weave.works>
2021-01-27 17:34:26 +01:00
Hidde Beydals
29c46a9892 Merge pull request #791 from SomtochiAma/refactor-reconcile-commands
Refactor suspend commands
2021-01-27 10:05:32 +01:00
Somtochi Onyekwere
ef579fe596 Refactor suspend commands
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-27 09:57:37 +01:00
Hidde Beydals
5b268f62a3 Merge pull request #789 from SomtochiAma/refactor-delete-command 2021-01-27 09:57:17 +01:00
Somtochi Onyekwere
1f1c8286a5 Refactor delete command for kustomizations, sources and helmreleases
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-27 09:43:39 +01:00
Hidde Beydals
5401e1ace4 Merge pull request #794 from fluxcd/get-type-fixes
Use correct type in various get source commands
2021-01-27 09:37:25 +01:00
Hidde Beydals
69294ef56d Use correct type in various get source commands
This fixes a bug where the wrong type was displayed for various
`get source` commands.

```console
$ flux get sources helm --namespace default
✗ no Bucket objects found in default namespace
```

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-27 09:28:48 +01:00
Hidde Beydals
a685ed8029 Merge pull request #793 from fluxcd/reconcile-w-source-other-ns
Set source namespace when reconciling with source
2021-01-27 09:21:12 +01:00
Hidde Beydals
68d0be3818 Set source namespace when reconciling with source
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-26 22:21:40 +01:00
Michael Bridgen
84e2cb4c1f Merge pull request #788 from fluxcd/create-secret-tls
Create secret for TLS command
2021-01-26 17:33:50 +00:00
Michael Bridgen
263c664acd Factor out more common secrets command code
Making the secret without data is always the same, so factor that out.

Signed-off-by: Michael Bridgen <michael@weave.works>
2021-01-26 17:25:33 +00:00
Michael Bridgen
b12c4c22fb Add command for creating TLS secrets
The image-reflector controller now accepts a secret containing a
client certificate and key, and/or a CA certificate; so it's useful to
have a command for creating them.

`flux create secret helm` is close, but accepts username/password
(which would be ignored), and has the wrong name of course. Happily
though, much can be shared between the implementations.

Signed-off-by: Michael Bridgen <michael@weave.works>
2021-01-26 17:25:33 +00:00
Stefan Prodan
9f39fadb9e Merge pull request #787 from fluxcd/fix-rbac-namespace
RBAC Fix: Replace SA namespace in ClusterRoleBindings
2021-01-26 19:21:19 +02:00
Stefan Prodan
4c29a1ca27 Replace SA namespace in RBAC
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-26 18:57:36 +02:00
Stefan Prodan
f4db124d50 Merge pull request #783 from fluxcd/rbac-fix
RBAC Fix: Add SA namespace to cluster role bindings
2021-01-26 16:24:16 +02:00
Stefan Prodan
8f8c7cccc6 Add SA namespace to RBAC
Fix flux install when not all controllers have been selected

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-26 16:16:34 +02:00
Hidde Beydals
e2097c28bd Merge pull request #782 from fluxcd/docs-img-auto-links
docs: fix image automation menu links
2021-01-26 14:34:29 +01:00
Hidde Beydals
871eb444fc docs: fix image automation menu links
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-26 14:26:18 +01:00
Hidde Beydals
bc5fbb9fa5 Merge pull request #774 from fluxcd/doc-helm-storage-ns 2021-01-26 14:15:00 +01:00
Hidde Beydals
3e9749c6b1 Document StorageNamespace in Helm migration guide
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-26 14:05:54 +01:00
Hidde Beydals
eaf08fbe90 Merge pull request #781 from fluxcd/docs-image-update 2021-01-26 14:05:47 +01:00
Hidde Beydals
df411cdb88 Update alpha warning
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-26 13:54:28 +01:00
Hidde Beydals
a83c26a864 Provide link to CalVer, build ID, etc. policies
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-26 13:54:28 +01:00
Hidde Beydals
6aa853491c Add image automation to menu
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-26 13:54:28 +01:00
Hidde Beydals
3af1d7a5b8 Merge pull request #771 from fluxcd/update-components 2021-01-26 13:53:47 +01:00
fluxcdbot
c7c57f6717 Update toolkit components 2021-01-26 12:32:15 +00:00
Stefan Prodan
cbb97768d1 Merge pull request #777 from fluxcd/dedicated-service-accounts
Add a dedicated service account per controller
2021-01-26 14:31:38 +02:00
Stefan Prodan
2bb40d335b Replace the default service account in docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-26 12:57:00 +02:00
Stefan Prodan
1b581d6f51 Add dedicated service accounts per controller
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-26 12:27:58 +02:00
Stefan Prodan
c219eb2883 Merge pull request #776 from SomtochiAma/refactor-get-commands
Refactor get command for kustomization and helmrelease
2021-01-26 11:55:27 +02:00
Somtochi Onyekwere
65a2c87b5a Refactor get cmd for kustomization and helmrelease
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-26 10:15:35 +01:00
Stefan Prodan
13aa523c37 Merge pull request #761 from SomtochiAma/cli-refactoring
Refactor get source commands
2021-01-26 10:42:49 +02:00
Somtochi Onyekwere
584f0eea58 Refactor get source commands
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-26 09:26:55 +01:00
Stefan Prodan
b5ebdb16b2 Merge pull request #773 from nairb774/sharp_payne
Add permissions for controller leader election
2021-01-25 23:35:19 +02:00
Brian Atkinson
a18f84c27b Add permissions for controller leader election.
The v0.8.0 version of the controller-runtime uses both config maps and
leases to perform leader election. These permissions seem to be in the
individual controller repos, but not here. For example
2d38de8779/config/rbac/leader_election_role.yaml (L33-L44)

Signed-off-by: Brian Atkinson <brian@atkinson.mn>
2021-01-25 10:15:57 -08:00
Stefan Prodan
eb86505375 Merge pull request #769 from fluxcd/update-release-doc
Add the image automation controllers to release doc
2021-01-25 15:31:31 +02:00
Stefan Prodan
984f1c8669 Add the image automation controllers to release doc
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-25 15:21:43 +02:00
Hidde Beydals
c7378edabc Merge pull request #765 from fluxcd/update-controllers 2021-01-22 16:35:30 +00:00
Hidde Beydals
ac6c6e3a30 Incorporate required API changes
* Use `LocalObjectReference` and `NamespacedObjectKindReference`
  from `meta` package, as required by controller API changes.
* Remove `Update` field from created `ImageUpdateAutomation`,
  as the API changed and the default is now defined in the Custom
  Resource Definition.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-22 17:21:36 +01:00
fluxcdbot
1bca31f32c Update toolkit components 2021-01-22 16:07:19 +00:00
Hidde Beydals
28d099a6b2 Merge pull request #741 from fluxcd/include-image-spec-docs 2021-01-22 16:06:33 +00:00
Michael Bridgen
6a2b53fbf5 Put image specs and refs in place
This adds the image controller specs and refs to the workflow that
prepares the docs, and links to them in the index.

Signed-off-by: Michael Bridgen <michael@weave.works>
2021-01-22 16:49:18 +01:00
Michael Bridgen
7568f301b8 Make home for image-* component docs
This has a description of the controllers, and is where the API specs
and refs will get copied to.

Signed-off-by: Michael Bridgen <michael@weave.works>
2021-01-22 16:49:18 +01:00
Stefan Prodan
6add511447 Merge pull request #755 from SomtochiAma/cli-refactoring
Refactor cmd global variables into structs
2021-01-22 12:27:39 +02:00
Somtochi Onyekwere
a7586e69fd Refactor cmd global variables into structs
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-22 10:42:31 +01:00
Stefan Prodan
77db369213 Merge pull request #757 from fluxcd/gitlab-fix
Update fluxcd/pkg/git to v0.2.3
2021-01-21 18:12:11 +02:00
Stefan Prodan
2eb6a0513e Update fluxcd/pkg/git to v0.2.3
Fixes Gitlab bootstrap when used with a project token

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-21 17:51:07 +02:00
Stefan Prodan
38468c72db Merge pull request #750 from SomtochiAma/kms-docs
Update guide for mozilla sops
2021-01-20 17:04:56 +02:00
Somtochi Onyekwere
5f759200c6 update guide for mozilla sops
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-20 15:54:26 +01:00
Daniel Holbach
81f68157fa Merge pull request #753 from dholbach/f-community-24
move talk info, part of fluxcd/community#24
2021-01-20 15:21:15 +01:00
Daniel Holbach
0f3e8abf06 move talk info, part of fluxcd/community#24
Signed-off-by: Daniel Holbach <daniel@weave.works>
2021-01-20 15:07:08 +01:00
Stefan Prodan
fcadd5312d Merge pull request #747 from mewzherder/patch-8
Clarify options on our GH Discussions page
2021-01-20 10:59:22 +02:00
mewzherder
6e32820910 Clarify options on our GH Discussions page
Signed-off-by: mewzherder <tamao@weave.works>
2021-01-19 22:27:50 -08:00
Hidde Beydals
8499269bab Merge pull request #744 from fluxcd/update-components
Update kustomize-controller to v0.6.3
2021-01-19 16:47:52 +01:00
fluxcdbot
72122120cb Update toolkit components 2021-01-19 15:37:38 +00:00
Hidde Beydals
2bbcb95f4f Merge pull request #728 from fluxcd/update-components
Update toolkit components
2021-01-19 14:29:22 +01:00
fluxcdbot
19982fa4a6 Update toolkit components 2021-01-19 13:04:46 +00:00
Hidde Beydals
41cf38c6a7 Merge pull request #740 from fluxcd/fluxcd/pkg/git-v0.2.2
Update fluxcd/pkg/git to v0.2.2
2021-01-19 14:03:43 +01:00
Stefan Prodan
16e74647eb Update fluxcd/pkg/git to v0.2.2
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-19 14:36:11 +02:00
Hidde Beydals
53cdcbc4ee Merge pull request #722 from fluxcd/workflow-tweaks
Fire 'Update Components' workflow on push to main
2021-01-15 17:34:56 +01:00
Hidde Beydals
eb2535e066 Fire 'Update Components' workflow on push to main
This results in the automatic rebase of an outstanding PR on merges to
this branch, removing the need of manually firing it.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-15 17:20:16 +01:00
Hidde Beydals
f5c29a7a72 Merge pull request #720 from fluxcd/update-components
Update kustomize-controller to v0.6.2
2021-01-15 17:16:50 +01:00
fluxcdbot
e243df93f1 Update toolkit components 2021-01-15 16:02:02 +00:00
Hidde Beydals
388642d9dd Merge pull request #721 from fluxcd/git-impl-fixes 2021-01-15 17:01:08 +01:00
Hidde Beydals
9e1db06936 Move Git implementation validation to custom flag
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-15 16:50:23 +01:00
Hidde Beydals
a260403334 Remove GitImplementation default
As the field in the CRD is optional.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-15 16:49:26 +01:00
Stefan Prodan
6396b25886 Merge pull request #641 from jonaskello/patch-1
Link docs how to get zsh/fish/ps completion to work in getting-started
2021-01-15 15:12:11 +02:00
Jonas Kello
ca480164b7 Merge branch 'main' into patch-1 2021-01-15 13:55:42 +01:00
Jonas Kello
714f9df3cf Link docs how to get zsh completion to work in getting-started
I tried to make completions work in zsh by just adding the same code as for the bash example but of course switching bash for zsh but it did not work. When I googled and dug deeper I finally found the answer in the deeper docs here:

https://github.com/fluxcd/flux2/blob/main/docs/cmd/flux_completion_zsh.md

The command in there works if I add it to my .zshrc file. I think linking to these specific docs may prevent others from just assuming it will work the same in zsh.

Signed-off-by: Jonas Kello <jonas.kello@gmail.com>
2021-01-15 13:17:29 +01:00
Hidde Beydals
6a7f1e7d48 Merge pull request #714 from fluxcd/tag-filter 2021-01-15 12:32:07 +01:00
Stefan Prodan
bcdce02b78 Add image tags regex filter arg to policy command
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-15 13:24:59 +02:00
Stefan Prodan
2f0835b655 Make branch arg required for image updates
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-15 13:22:43 +02:00
Hidde Beydals
36bafa23df Merge pull request #709 from SomtochiAma/gcp-kms-docs
Add GCP docs for Mozilla SOPS
2021-01-15 12:21:59 +01:00
Somtochi Onyekwere
db611549f2 Add GCP docs for Mozilla SOPS
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-15 12:13:00 +01:00
Hidde Beydals
5d4cdcc207 Merge pull request #713 from fluxcd/doc-fixes
docs: styling of hint blocks
2021-01-15 12:12:32 +01:00
Hidde Beydals
a3b9c094b6 docs: styling of hint blocks
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-15 12:03:55 +01:00
Hidde Beydals
f82174adae Merge pull request #680 from fluxcd/goreleaser-project-name
Configure `project_name` for GoReleaser
2021-01-15 12:01:55 +01:00
Hidde Beydals
c7080d2834 Configure project_name for GoReleaser
This causes the format of the checksum file generated during the release
to change from `flux2_*_checksums.txt` to `flux_*_checksums.txt`.

The configuration change is made through `project_name` and not via the
`checksum.name_template` setting, because a single checksum file is
generated during the release process.

The download and/or installation script in `install/flux.sh` has been
adapted to assume the new filename starting with MINOR version `0.6.0`.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-15 11:54:18 +01:00
Stefan Prodan
381127d413 Merge pull request #712 from fluxcd/reconcile-request-annotation
Use reconcile request annotation
2021-01-15 12:49:56 +02:00
Stefan Prodan
f6fa468acb Use reconcile request annotation
Replace deprecated `ReconcileAtAnnotation` annotation with `ReconcileRequestAnnotation`

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-15 12:41:13 +02:00
Hidde Beydals
9228130f92 Merge pull request #684 from fluxcd/multi-arch-image
Deprecate arch flags in favor of multi-arch images
2021-01-15 11:40:24 +01:00
Hidde Beydals
207c50ceac Deprecate arch flags in favor of multi-arch images
This commit deprecates the architecture flag (`--arch`) for the install
and bootstrap commands, in favor of the bundled multi-arch images that
will be available for the next MINOR range of GOTK controller releases.

Summary of changes:

* `*Arch` variables have been marked as deprecated for both commands.
* `-arm64` suffix is no longer selectively added to the image definition
  of a component's `Deployment`.
* `kubernetes.io/arch` node selector with the defined value has been
  removed from the components' `Deployment`s.
* `Arch` has been removed from the available `Options` in
  `manifestgen/install`.
* Documentation references have been changed to highlight existence
  of multi-arch images and supported architectures.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-01-15 11:25:20 +01:00
Hidde Beydals
c3255a6e1e Merge pull request #711 from fluxcd/image-policy-e2e 2021-01-15 11:24:51 +01:00
Stefan Prodan
5e1c93a167 Add e2e tests for image repository and policy
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-15 12:03:47 +02:00
Hidde Beydals
43c33a0cc3 Merge pull request #686 from fluxcd/update-components
Update toolkit components
2021-01-15 11:02:14 +01:00
fluxcdbot
f5117329e4 Update toolkit components 2021-01-15 09:49:23 +00:00
Hidde Beydals
f7c62d12a5 Merge pull request #651 from Sijoma/main
Typo fix in image-update documentation
2021-01-15 10:43:12 +01:00
Simon Zengerling
fe5f181706 fix(image-update.md): typo maker => marker
Signed-off-by: Simon Zengerling <simon.zengerling@lht.dlh.de>
2021-01-15 10:26:28 +01:00
Stefan Prodan
cc09b29a2e Merge pull request #705 from SomtochiAma/custom-domain-gitlab
Update pkg/git to v0.2.1
2021-01-15 09:49:05 +02:00
Somtochi Onyekwere
406601eead Update pkg/git to v0.2.2
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-14 20:15:17 +01:00
Stefan Prodan
341d860c51 Merge pull request #708 from fluxcd/allow-egress-between-flux-pods
Allow egress traffic for controller pods
2021-01-14 14:17:15 +02:00
Stefan Prodan
8214bb8e33 Allow egress traffic for controller pods
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-14 13:38:38 +02:00
Stefan Prodan
884e3c678c Merge pull request #702 from fluxcd/feature/git-implementation
Add git implementation to generate sync options
2021-01-14 10:49:37 +02:00
Philip Laine
3b249dfe69 Change default to use const
Signed-off-by: Philip Laine <philip.laine@gmail.com>
2021-01-14 00:21:59 +01:00
Philip Laine
d236a9af57 Add git implementation to generate sync options
Signed-off-by: Philip Laine <philip.laine@gmail.com>
2021-01-13 23:44:29 +01:00
Hidde Beydals
ad9b0ae067 Merge pull request #698 from staceypotter/patch-6
Moved 11 Jan talk from Upcoming to Featured
2021-01-13 19:47:33 +01:00
Stacey Potter
31f166cd02 Moved 11 Jan talk from Upcoming to Featured
Updated and moved this from Upcoming Events: 
- 11 Jan 2021 - [Helm + GitOps = ️ with Scott Rigby](https://www.meetup.com/GitOps-Community/events/275348736/)

To this in Featured Talks:
- 11 Jan 2021 - [Helm + GitOps = ️ with Scott Rigby](https://youtu.be/YG8jMFrYQvM)

Signed-off-by: Stacey Potter <50154848+staceypotter@users.noreply.github.com>
2021-01-13 16:33:42 +01:00
Hidde Beydals
5685ebc3a5 Merge pull request #678 from staceypotter/patch-5
Added Jan 25 Meetup to Upcoming Events section
2021-01-13 16:32:39 +01:00
Stacey Potter
46bcf5da33 Added Jan 25 Meetup to Upcoming Events section
Signed-off-by: Stacey Potter <50154848+staceypotter@users.noreply.github.com>
2021-01-13 15:49:35 +01:00
Michael Bridgen
1784d15f36 Merge pull request #696 from fluxcd/controller-runtime-070
Update to controller-runtime 0.7.0
2021-01-13 11:50:58 +00:00
Michael Bridgen
cafce536bb Rename asRuntime* -> asClient*
For the avoidance of misdirection.

Signed-off-by: Michael Bridgen <michael@weave.works>
2021-01-13 11:38:02 +00:00
Michael Bridgen
d03280a12f Update to controller-runtime 0.7.0
controller-runtime methods now accept `client.Object` and
`client.ObjectList` rather than `runtime.Object`. This means the
adapter interfaces need to change signature, but happily, little else.

Since the list adapter is now distinct to the object adapter, `len()`
can go there instead of the command-specific interfaces.

Signed-off-by: Michael Bridgen <michael@weave.works>
2021-01-13 11:25:40 +00:00
Stefan Prodan
b30ef523f8 Merge pull request #688 from SomtochiAma/gcp-docs
Add GCR auth to image update guide
2021-01-13 12:25:32 +02:00
Somtochi Onyekwere
a6a303629a Add doc for authenticating gcr
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-13 11:15:13 +01:00
Stefan Prodan
61e79ef793 Merge pull request #694 from fluxcd/fix-hr-docs
Remove deprecated source behaviour
2021-01-13 11:45:39 +02:00
Stefan Prodan
f632abd8fa Remove deprecated source behaviour
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-13 11:26:00 +02:00
Philip Laine
c3911fe490 Merge pull request #668 from fluxcd/update/commit-status-guide
Update commit status notification guide
2021-01-12 22:52:59 +01:00
Philip Laine
505701e1c6 Fix cluster path in examples
Signed-off-by: Philip Laine <philip.laine@gmail.com>
2021-01-12 22:15:51 +01:00
Philip Laine
67643e7487 Minimize deployment yaml
Signed-off-by: Philip Laine <philip.laine@gmail.com>
2021-01-12 15:21:28 +01:00
Philip Laine
a962c17adb Add gitlab screenshots
Signed-off-by: Philip Laine <philip.laine@gmail.com>
2021-01-12 15:21:28 +01:00
Philip Laine
479b4b5859 Update commit status notification guide
Signed-off-by: Philip Laine <philip.laine@gmail.com>
2021-01-12 15:21:28 +01:00
Hidde Beydals
57f8cf85ca Merge pull request #687 from Kissy/main
docs: update sealed-secrets chart URL
2021-01-12 15:16:05 +01:00
Guillaume Le Biller
dd2c20b225 Update sealed-secrets chart URL
Signed-off-by: Guillaume Le Biller <glebiller@Traveldoo.com>
2021-01-12 15:03:03 +01:00
Stefan Prodan
9da427a515 Merge pull request #682 from SomtochiAma/multiple-config-files
Check for multiple files in KUBECONFIG variable
2021-01-12 11:39:53 +02:00
Somtochi Onyekwere
604773e866 check for multiple files in KUBECONFIG variable
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-12 10:26:29 +01:00
Hidde Beydals
1331f5260a Merge pull request #683 from fluxcd/fix-azure-url
Fix Azure DevOps URL in docs
2021-01-11 20:13:24 +01:00
Stefan Prodan
65d0f3569c Fix Azure DevOps URL in docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-11 19:51:00 +02:00
Stefan Prodan
ba522877ec Merge pull request #681 from SomtochiAma/gitlab-path
Fix GitLab bootstrap when used with sub-groups
2021-01-11 17:36:18 +02:00
Somtochi Onyekwere
4b63ccf140 Update fluxcd/pkg/git
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-11 16:17:07 +01:00
Somtochi Onyekwere
4fcf93306a Gets actual path for owner
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-11 15:02:02 +01:00
Hidde Beydals
96d8ec2016 Merge pull request #679 from staceypotter/patch-4
Added Jan 25 Meetup to Upcoming Events section
2021-01-11 09:55:59 +01:00
Stacey Potter
3697a5e348 Added Jan 25 Meetup to Upcoming Events section
Signed-off-by: Stacey Potter <50154848+staceypotter@users.noreply.github.com>
2021-01-10 14:07:09 -05:00
Stefan Prodan
084c587c0e Merge pull request #675 from fluxcd/docs-fixes
Add generated manifests to get started guide
2021-01-08 17:57:09 +02:00
Stefan Prodan
dcb505045e Add generated manifests to get started guide
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-08 17:46:00 +02:00
Stefan Prodan
0aeb3128ed Fix semver range prerelease examples
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-08 17:45:31 +02:00
Stefan Prodan
c61cfcbd18 Merge pull request #608 from SomtochiAma/getting-started-guide
Simplify the getting started guide
2021-01-08 16:16:07 +02:00
Somtochi Onyekwere
eba2dd36e0 Update getting started guide
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-08 14:46:49 +01:00
Stefan Prodan
2a75754561 Merge pull request #673 from fluxcd/doc-signoff
Add DCO signoff ref to contributing doc
2021-01-08 13:52:09 +02:00
Stefan Prodan
d03944893d Add DCO signoff ref to contributing doc
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-08 13:12:07 +02:00
Stefan Prodan
884c6ebd37 Merge pull request #672 from fluxcd/create-secret-helm
Add create secret helm command
2021-01-08 12:51:34 +02:00
Stefan Prodan
331ac3f031 Add create secret helm command
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-08 12:36:11 +02:00
Stefan Prodan
ccc84a8367 Merge pull request #671 from SomtochiAma/incorrect-tf-path
Fix manifests path on Windows
2021-01-08 12:23:31 +02:00
Somtochi Onyekwere
daeb41c31b Uses path instead of filepath
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-08 11:13:27 +01:00
Stefan Prodan
17bda9c110 Merge pull request #670 from fluxcd/fix-secret-git
Map ecdsa/ed25519 args to create secret
2021-01-08 11:34:20 +02:00
Stefan Prodan
febedaad8f Map ecdsa/ed25519 args to create secret
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-08 11:20:36 +02:00
Stefan Prodan
d1357dff1f Merge pull request #664 from fluxcd/source-watcher
Update dev guide to point to fluxcd/source-watcher
2021-01-07 14:35:45 +02:00
Stefan Prodan
102552427f Update dev guide to point to fluxcd/source-watcher
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-01-07 14:19:19 +02:00
Michael Bridgen
f33898265d Merge pull request #663 from fluxcd/update-img-roadmap
Cross off tasks in the image automation roadmap
2021-01-07 12:06:04 +00:00
Michael Bridgen
57bdaf939a Cross off tasks in the image automation roadmap
Specifically:
 - integration into `flux` CLI
 - guide to how to use the controllers

Signed-off-by: Michael Bridgen <michael@weave.works>
2021-01-07 11:45:46 +00:00
Stefan Prodan
981fed111b Merge pull request #660 from SomtochiAma/check-bootstrap-path
Checks if bootstrap path differs
2021-01-07 12:08:54 +02:00
Somtochi Onyekwere
3a4a2002d4 Corrects typo
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-07 10:44:40 +01:00
Somtochi Onyekwere
b8d4af5538 Inform user of path being used
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-07 10:30:17 +01:00
Somtochi Onyekwere
0646538cef Checks if bootstrap path differs
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-07 10:22:13 +01:00
Stefan Prodan
70a87247e2 Merge pull request #658 from SomtochiAma/incorrect-windows-file-path
Coverts backward slash to forward slash in path flag
2021-01-07 11:17:51 +02:00
Somtochi Onyekwere
61129c6b6a Coverts backward slash to forward slash in path flag
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-07 09:29:37 +01:00
Aurel Canciu
c158f95130 Merge pull request #657 from fluxcd/image-repo-auth-docs
Add documentation for ECR authentication
2021-01-06 18:38:41 +02:00
Aurel Canciu
ad90d37f14 Add documentation for ECR authentication
Document a workaround solution for users to rely on until native image
repository authentication is implemented for supported cloud providers.

Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2021-01-06 18:19:51 +02:00
Stefan Prodan
73ba754481 Merge pull request #656 from pepesenaris/patch-1
Minor fixes to core concepts doc
2021-01-06 17:53:18 +02:00
Jose Javier Señaris
7dcfbdbb29 Minor fixes
Fix small typo.
Added verb to make the intent clear
Added spaces before "(" to match the same pattern used in other parts of the docs

Signed-off-by: Jose Javier Senaris Carballo <pepe.senaris@alayacare.com>
2021-01-06 10:44:32 -05:00
Philip Laine
f453507fcc Merge pull request #654 from fluxcd/update/helm-cloud-storage
Update helm release guide
2021-01-06 14:25:56 +01:00
Philip Laine
c5465de000 Update helm release guide with how to use cloud storage backends
Signed-off-by: Philip Laine <philip.laine@gmail.com>
2021-01-06 12:35:45 +01:00
Stefan Prodan
352b864636 Merge pull request #652 from fluxcd/update-components
Update image-automation-controller to v0.2.0
2021-01-06 12:19:23 +02:00
fluxcdbot
c034befbb5 Update toolkit components 2021-01-06 09:59:33 +00:00
Stefan Prodan
572cdf40fc Merge pull request #649 from staceypotter/patch-4
Update upcoming events & featured talks section
2021-01-06 10:27:46 +02:00
Stacey Potter
0c0d353e9c Update upcoming events & featured talks section
Put things in chronological order, added new events, moved old ones, switched order of the 2 sections to match fluxcd/flux2 

Signed-off-by: Stacey Potter <50154848+staceypotter@users.noreply.github.com>
2021-01-05 13:21:20 -05:00
Daniel Holbach
bcc90afba2 Merge pull request #640 from staceypotter/patch-4
Updated Upcoming Events & Featured Talks sections
2021-01-05 17:35:18 +01:00
Stacey Potter
a919703011 Update Upcoming Events & Featured Talks sections
Sorted 'featured talks' chronologically, moved Dec 14 talk from upcoming to featured section with video link, added Scott's upcoming Helm talk in Jan 2021.
Added 2-Nov-2020 video that was missing

Signed-off-by: Stacey Potter <50154848+staceypotter@users.noreply.github.com>
2021-01-05 09:04:02 -07:00
Stefan Prodan
3300a45c39 Merge pull request #648 from SomtochiAma/validate-project-name
Validates project name for gitlab
2021-01-05 16:04:34 +02:00
Somtochi Onyekwere
f1cfae8f26 Validates project name for gitlab
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-05 14:37:49 +01:00
Stefan Prodan
62763961be Merge pull request #600 from SomtochiAma/core-concepts-docs
Add core concepts to docs
2021-01-05 12:45:09 +02:00
Somtochi Onyekwere
f1dab2279d Small nits
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-05 11:36:51 +01:00
Somtochi Onyekwere
ea337cf839 Initial docs for core concepts
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-05 11:30:22 +01:00
Stefan Prodan
27277136f8 Merge pull request #647 from SomtochiAma/validate-components
Validates components set
2021-01-05 12:05:36 +02:00
Somtochi Onyekwere
dd0b807fe4 Validates components set
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>

Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
2021-01-05 10:52:11 +01:00
Michael Bridgen
ed09dd57b6 Merge pull request #646 from fluxcd/auto-docs-correction
Make sure flux create output gets redirected to file
2021-01-04 15:17:39 +00:00
Michael Bridgen
58b4c980c1 Make sure flux create output gets redirected to file
(i.e.: a missing `>`)

Signed-off-by: Michael Bridgen <michael@weave.works>
2021-01-04 15:00:17 +00:00
169 changed files with 3762 additions and 3158 deletions

46
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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
View 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
View 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

View File

@@ -54,6 +54,18 @@ jobs:
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1beta1/receiver.md" > docs/components/notification/receiver.md curl -# -Lf "https://raw.githubusercontent.com/fluxcd/notification-controller/$NOTIFICATION_VER/docs/spec/v1beta1/receiver.md" > docs/components/notification/receiver.md
} }
{
# image-*-controller CRDs; these use the same API group
IMG_REFL_VER=$(controller_version image-reflector-controller)
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/image-reflector-controller/$IMG_REFL_VER/docs/api/image-reflector.md" > docs/components/image/reflector-api.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/image-reflector-controller/$IMG_REFL_VER/docs/spec/v1alpha1/imagerepositories.md" > docs/components/image/imagerepositories.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/image-reflector-controller/$IMG_REFL_VER/docs/spec/v1alpha1/imagepolicies.md" > docs/components/image/imagepolicies.md
IMG_AUTO_VER=$(controller_version image-automation-controller)
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/image-automation-controller/$IMG_AUTO_VER/docs/api/image-automation.md" > docs/components/image/automation-api.md
curl -# -Lf "https://raw.githubusercontent.com/fluxcd/image-automation-controller/$IMG_AUTO_VER/docs/spec/v1alpha1/imageupdateautomations.md" > docs/components/image/imageupdateautomations.md
}
{ {
# install script # install script
cp install/flux.sh docs/install.sh cp install/flux.sh docs/install.sh

View File

@@ -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 \
@@ -146,6 +161,20 @@ jobs:
--chart=podinfo \ --chart=podinfo \
--chart-version="5.0.x" \ --chart-version="5.0.x" \
--service-account=dev-team --service-account=dev-team
- name: flux create image repository
run: |
./bin/flux create image repository podinfo \
--image=ghcr.io/stefanprodan/podinfo \
--interval=1m
- name: flux create image policy
run: |
./bin/flux create image policy podinfo \
--image-ref=podinfo \
--interval=1m \
--semver=5.0.x
- name: flux get image policy
run: |
./bin/flux get image policy podinfo | grep '5.0.3'
- name: flux2-kustomize-helm-example - name: flux2-kustomize-helm-example
run: | run: |
./bin/flux create source git flux-system \ ./bin/flux create source git flux-system \

View File

@@ -4,6 +4,8 @@ on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: "0 * * * *" - cron: "0 * * * *"
push:
branches: [main]
jobs: jobs:
update-components: update-components:

View File

@@ -1,3 +1,4 @@
project_name: flux
builds: builds:
- <<: &build_defaults - <<: &build_defaults
binary: flux binary: flux

View File

@@ -1,7 +1,6 @@
# Contributing # Contributing
Flux is [Apache 2.0 Flux is [Apache 2.0 licensed](https://github.com/fluxcd/flux2/blob/main/LICENSE) and
licensed](https://github.com/fluxcd/flux2/blob/main/LICENSE) and
accepts contributions via GitHub pull requests. This document outlines accepts contributions via GitHub pull requests. This document outlines
some of the conventions on to make it easier to get your contribution some of the conventions on to make it easier to get your contribution
accepted. accepted.
@@ -14,9 +13,18 @@ code.
By contributing to this project you agree to the Developer Certificate of By contributing to this project you agree to the Developer Certificate of
Origin (DCO). This document was created by the Linux Kernel community and is a Origin (DCO). This document was created by the Linux Kernel community and is a
simple statement that you, as a contributor, have the legal right to make the simple statement that you, as a contributor, have the legal right to make the
contribution. No action from you is required, but it's a good idea to see the contribution.
[DCO](DCO) file for details before you start contributing code to FluxCD
organization. We require all commits to be signed. By signing off with your signature, you
certify that you wrote the patch or otherwise have the right to contribute the
material by the rules of the [DCO](DCO):
`Signed-off-by: Jane Doe <jane.doe@example.com>`
The signature must contain your real name
(sorry, no pseudonyms or anonymous contributions)
If your `user.name` and `user.email` are configured in your Git config,
you can sign your commit automatically with `git commit -s`.
## Communications ## Communications

View File

@@ -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,28 +99,22 @@ 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
- Join the [planning discussions](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
### Upcoming Events ### Events
- 14 Dec 2020 - [The Power of GitOps with Flux and Flagger with Leigh Capili](https://www.meetup.com/GitOps-Community/events/274924513/) Check out our **[events calendar](https://fluxcd.io/community/#talks)**, both with upcoming talks you can attend or past events videos you can watch.
### Featured Talks We look forward to seeing you with us!
- 24 Nov 2020 - [Flux CD v2 with GitOps Toolkit - Kubernetes Deployment and Sync Mechanism](https://youtu.be/R6OeIgb7lUI)
- 28 Oct 2020 - [The Kubelist Podcast: Flux with Michael Bridgen](https://www.heavybit.com/library/podcasts/the-kubelist-podcast/ep-5-flux-with-michael-bridgen-of-weaveworks/)
- 19 Oct 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 1 with Leigh Capili](https://youtu.be/0v5bjysXTL8)
- 30 Nov 2020 - [The Power of GitOps with Flux 2 - Part 3 with Leigh Capili](https://youtu.be/N_K5g7o9JKg)
- 12 Oct 2020 - [Rawkode Live: Introduction to GitOps Toolkit with Stefan Prodan](https://youtu.be/HqTzuOBP0eY)
- 4 Sep 2020 - [KubeCon Europe: The road to Flux v2 and Progressive Delivery with Stefan Prodan & Hidde Beydals](https://youtu.be/8v94nUkXsxU)
- 25 June 2020 - [Cloud Native Nordics: Introduction to GitOps & GitOps Toolkit with Alexis Richardson & Stefan Prodan](https://youtu.be/qQBtSkgl7tI)
We are looking forward to seeing you with us!

View File

@@ -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)"

View File

@@ -45,89 +45,103 @@ var bootstrapCmd = &cobra.Command{
Long: "The bootstrap sub-commands bootstrap the toolkit components on the targeted Git provider.", Long: "The bootstrap sub-commands bootstrap the toolkit components on the targeted Git provider.",
} }
var ( type bootstrapFlags struct {
bootstrapVersion string version string
bootstrapDefaultComponents []string defaultComponents []string
bootstrapExtraComponents []string extraComponents []string
bootstrapRegistry string registry string
bootstrapImagePullSecret string imagePullSecret string
bootstrapBranch string branch string
bootstrapWatchAllNamespaces bool watchAllNamespaces bool
bootstrapNetworkPolicy bool networkPolicy bool
bootstrapManifestsPath string manifestsPath string
bootstrapArch = flags.Arch(defaults.Arch) arch flags.Arch
bootstrapLogLevel = flags.LogLevel(defaults.LogLevel) logLevel flags.LogLevel
bootstrapRequiredComponents = []string{"source-controller", "kustomize-controller"} requiredComponents []string
bootstrapTokenAuth bool tokenAuth bool
bootstrapClusterDomain string clusterDomain string
) }
const ( const (
bootstrapDefaultBranch = "main" bootstrapDefaultBranch = "main"
) )
var bootstrapArgs = NewBootstrapFlags()
func init() { func init() {
bootstrapCmd.PersistentFlags().StringVarP(&bootstrapVersion, "version", "v", defaults.Version, bootstrapCmd.PersistentFlags().StringVarP(&bootstrapArgs.version, "version", "v", rootArgs.defaults.Version,
"toolkit version") "toolkit version")
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapDefaultComponents, "components", defaults.Components, bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, "components", rootArgs.defaults.Components,
"list of components, accepts comma-separated values") "list of components, accepts comma-separated values")
bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapExtraComponents, "components-extra", nil, bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil,
"list of components in addition to those supplied or defaulted, accepts comma-separated values") "list of components in addition to those supplied or defaulted, accepts comma-separated values")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapRegistry, "registry", "ghcr.io/fluxcd", bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.registry, "registry", "ghcr.io/fluxcd",
"container registry where the toolkit images are published") "container registry where the toolkit images are published")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapImagePullSecret, "image-pull-secret", "", bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.imagePullSecret, "image-pull-secret", "",
"Kubernetes secret name used for pulling the toolkit images from a private registry") "Kubernetes secret name used for pulling the toolkit images from a private registry")
bootstrapCmd.PersistentFlags().Var(&bootstrapArch, "arch", bootstrapArch.Description()) bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.arch, "arch", bootstrapArgs.arch.Description())
bootstrapCmd.PersistentFlags().StringVar(&bootstrapBranch, "branch", bootstrapDefaultBranch, bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.branch, "branch", bootstrapDefaultBranch,
"default branch (for GitHub this must match the default branch setting for the organization)") "default branch (for GitHub this must match the default branch setting for the organization)")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapWatchAllNamespaces, "watch-all-namespaces", true, bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.watchAllNamespaces, "watch-all-namespaces", true,
"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed") "watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapNetworkPolicy, "network-policy", true, bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.networkPolicy, "network-policy", true,
"deny ingress access to the toolkit controllers from other namespaces using network policies") "deny ingress access to the toolkit controllers from other namespaces using network policies")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapTokenAuth, "token-auth", false, bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.tokenAuth, "token-auth", false,
"when enabled, the personal access token will be used instead of SSH deploy key") "when enabled, the personal access token will be used instead of SSH deploy key")
bootstrapCmd.PersistentFlags().Var(&bootstrapLogLevel, "log-level", bootstrapLogLevel.Description()) bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.logLevel, "log-level", bootstrapArgs.logLevel.Description())
bootstrapCmd.PersistentFlags().StringVar(&bootstrapManifestsPath, "manifests", "", "path to the manifest directory") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.manifestsPath, "manifests", "", "path to the manifest directory")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapClusterDomain, "cluster-domain", defaults.ClusterDomain, "internal cluster domain") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
bootstrapCmd.PersistentFlags().MarkHidden("manifests") bootstrapCmd.PersistentFlags().MarkHidden("manifests")
bootstrapCmd.PersistentFlags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64")
rootCmd.AddCommand(bootstrapCmd) rootCmd.AddCommand(bootstrapCmd)
} }
func NewBootstrapFlags() bootstrapFlags {
return bootstrapFlags{
logLevel: flags.LogLevel(rootArgs.defaults.LogLevel),
requiredComponents: []string{"source-controller", "kustomize-controller"},
}
}
func bootstrapComponents() []string { func bootstrapComponents() []string {
return append(bootstrapDefaultComponents, bootstrapExtraComponents...) return append(bootstrapArgs.defaultComponents, bootstrapArgs.extraComponents...)
} }
func bootstrapValidate() error { func bootstrapValidate() error {
components := bootstrapComponents() components := bootstrapComponents()
for _, component := range bootstrapRequiredComponents { for _, component := range bootstrapArgs.requiredComponents {
if !utils.ContainsItemString(components, component) { if !utils.ContainsItemString(components, component) {
return fmt.Errorf("component %s is required", component) return fmt.Errorf("component %s is required", component)
} }
} }
if err := utils.ValidateComponents(components); err != nil {
return err
}
return nil return nil
} }
func generateInstallManifests(targetPath, namespace, tmpDir string, localManifests string) (string, error) { func generateInstallManifests(targetPath, namespace, tmpDir string, localManifests string) (string, error) {
opts := install.Options{ opts := install.Options{
BaseURL: localManifests, BaseURL: localManifests,
Version: bootstrapVersion, Version: bootstrapArgs.version,
Namespace: namespace, Namespace: rootArgs.namespace,
Components: bootstrapComponents(), Components: bootstrapComponents(),
Registry: bootstrapRegistry, Registry: bootstrapArgs.registry,
ImagePullSecret: bootstrapImagePullSecret, ImagePullSecret: bootstrapArgs.imagePullSecret,
Arch: bootstrapArch.String(), WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
WatchAllNamespaces: bootstrapWatchAllNamespaces, NetworkPolicy: bootstrapArgs.networkPolicy,
NetworkPolicy: bootstrapNetworkPolicy, LogLevel: bootstrapArgs.logLevel.String(),
LogLevel: bootstrapLogLevel.String(), NotificationController: rootArgs.defaults.NotificationController,
NotificationController: defaults.NotificationController, ManifestFile: rootArgs.defaults.ManifestFile,
ManifestFile: defaults.ManifestFile, Timeout: rootArgs.timeout,
Timeout: timeout,
TargetPath: targetPath, TargetPath: targetPath,
ClusterDomain: bootstrapClusterDomain, ClusterDomain: bootstrapArgs.clusterDomain,
} }
if localManifests == "" { if localManifests == "" {
opts.BaseURL = defaults.BaseURL opts.BaseURL = rootArgs.defaults.BaseURL
} }
output, err := install.Generate(opts) output, err := install.Generate(opts)
@@ -144,13 +158,13 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes
func applyInstallManifests(ctx context.Context, manifestPath string, components []string) error { func applyInstallManifests(ctx context.Context, manifestPath string, components []string) error {
kubectlArgs := []string{"apply", "-f", manifestPath} kubectlArgs := []string{"apply", "-f", manifestPath}
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubeconfig, kubecontext, kubectlArgs...); err != nil { if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
return fmt.Errorf("install failed") return fmt.Errorf("install failed")
} }
for _, deployment := range components { for _, deployment := range components {
kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()} kubectlArgs = []string{"-n", rootArgs.namespace, "rollout", "status", "deployment", deployment, "--timeout", rootArgs.timeout.String()}
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubeconfig, kubecontext, kubectlArgs...); err != nil { if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
return fmt.Errorf("install failed") return fmt.Errorf("install failed")
} }
} }
@@ -186,20 +200,20 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
func applySyncManifests(ctx context.Context, kubeClient client.Client, name, namespace, manifestsPath string) error { func applySyncManifests(ctx context.Context, kubeClient client.Client, name, namespace, manifestsPath string) error {
kubectlArgs := []string{"apply", "-k", manifestsPath} kubectlArgs := []string{"apply", "-k", manifestsPath}
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, kubeconfig, kubecontext, kubectlArgs...); err != nil { if _, err := utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
return err return err
} }
logger.Waitingf("waiting for cluster sync") logger.Waitingf("waiting for cluster sync")
var gitRepository sourcev1.GitRepository var gitRepository sourcev1.GitRepository
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isGitRepositoryReady(ctx, kubeClient, types.NamespacedName{Name: name, Namespace: namespace}, &gitRepository)); err != nil { isGitRepositoryReady(ctx, kubeClient, types.NamespacedName{Name: name, Namespace: namespace}, &gitRepository)); err != nil {
return err return err
} }
var kustomization kustomizev1.Kustomization var kustomization kustomizev1.Kustomization
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isKustomizationReady(ctx, kubeClient, types.NamespacedName{Name: name, Namespace: namespace}, &kustomization)); err != nil { isKustomizationReady(ctx, kubeClient, types.NamespacedName{Name: name, Namespace: namespace}, &kustomization)); err != nil {
return err return err
} }
@@ -234,7 +248,7 @@ func shouldCreateDeployKey(ctx context.Context, kubeClient client.Client, namesp
} }
func generateDeployKey(ctx context.Context, kubeClient client.Client, url *url.URL, namespace string) (string, error) { func generateDeployKey(ctx context.Context, kubeClient client.Client, url *url.URL, namespace string) (string, error) {
pair, err := generateKeyPair(ctx) pair, err := generateKeyPair(ctx, sourceArgs.GitKeyAlgorithm, sourceArgs.GitRSABits, sourceArgs.GitECDSACurve)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -261,3 +275,20 @@ func generateDeployKey(ctx context.Context, kubeClient client.Client, url *url.U
return string(pair.PublicKey), nil return string(pair.PublicKey), nil
} }
func checkIfBootstrapPathDiffers(ctx context.Context, kubeClient client.Client, namespace string, path string) (string, bool) {
namespacedName := types.NamespacedName{
Name: namespace,
Namespace: namespace,
}
var fluxSystemKustomization kustomizev1.Kustomization
err := kubeClient.Get(ctx, namespacedName, &fluxSystemKustomization)
if err != nil {
return "", false
}
if fluxSystemKustomization.Spec.Path == path {
return "", false
}
return fluxSystemKustomization.Spec.Path, true
}

View File

@@ -23,6 +23,7 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -56,7 +57,7 @@ the bootstrap command will perform an upgrade if needed.`,
flux bootstrap github --owner=<organization> --repository=<repo name> --path=dev-cluster flux bootstrap github --owner=<organization> --repository=<repo name> --path=dev-cluster
# Run bootstrap for a public repository on a personal account # Run bootstrap for a public repository on a personal account
flux bootstrap github --owner=<user> --repository=<repo name> --private=false --personal=true flux bootstrap github --owner=<user> --repository=<repo name> --private=false --personal=true
# Run bootstrap for a private repo hosted on GitHub Enterprise using SSH auth # Run bootstrap for a private repo hosted on GitHub Enterprise using SSH auth
flux bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain> --ssh-hostname=<domain> flux bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain> --ssh-hostname=<domain>
@@ -70,35 +71,37 @@ the bootstrap command will perform an upgrade if needed.`,
RunE: bootstrapGitHubCmdRun, RunE: bootstrapGitHubCmdRun,
} }
var ( type githubFlags struct {
ghOwner string owner string
ghRepository string repository string
ghInterval time.Duration interval time.Duration
ghPersonal bool personal bool
ghPrivate bool private bool
ghHostname string hostname string
ghPath flags.SafeRelativePath path flags.SafeRelativePath
ghTeams []string teams []string
ghDelete bool delete bool
ghSSHHostname string sshHostname string
) }
const ( const (
ghDefaultPermission = "maintain" ghDefaultPermission = "maintain"
) )
func init() { var githubArgs githubFlags
bootstrapGitHubCmd.Flags().StringVar(&ghOwner, "owner", "", "GitHub user or organization name")
bootstrapGitHubCmd.Flags().StringVar(&ghRepository, "repository", "", "GitHub repository name")
bootstrapGitHubCmd.Flags().StringArrayVar(&ghTeams, "team", []string{}, "GitHub team to be given maintainer access")
bootstrapGitHubCmd.Flags().BoolVar(&ghPersonal, "personal", false, "is personal repository")
bootstrapGitHubCmd.Flags().BoolVar(&ghPrivate, "private", true, "is private repository")
bootstrapGitHubCmd.Flags().DurationVar(&ghInterval, "interval", time.Minute, "sync interval")
bootstrapGitHubCmd.Flags().StringVar(&ghHostname, "hostname", git.GitHubDefaultHostname, "GitHub hostname")
bootstrapGitHubCmd.Flags().StringVar(&ghSSHHostname, "ssh-hostname", "", "GitHub SSH hostname, to be used when the SSH host differs from the HTTPS one")
bootstrapGitHubCmd.Flags().Var(&ghPath, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
bootstrapGitHubCmd.Flags().BoolVar(&ghDelete, "delete", false, "delete repository (used for testing only)") func init() {
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.owner, "owner", "", "GitHub user or organization name")
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.repository, "repository", "", "GitHub repository name")
bootstrapGitHubCmd.Flags().StringArrayVar(&githubArgs.teams, "team", []string{}, "GitHub team to be given maintainer access")
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.personal, "personal", false, "is personal repository")
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.private, "private", true, "is private repository")
bootstrapGitHubCmd.Flags().DurationVar(&githubArgs.interval, "interval", time.Minute, "sync interval")
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.hostname, "hostname", git.GitHubDefaultHostname, "GitHub hostname")
bootstrapGitHubCmd.Flags().StringVar(&githubArgs.sshHostname, "ssh-hostname", "", "GitHub SSH hostname, to be used when the SSH host differs from the HTTPS one")
bootstrapGitHubCmd.Flags().Var(&githubArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
bootstrapGitHubCmd.Flags().BoolVar(&githubArgs.delete, "delete", false, "delete repository (used for testing only)")
bootstrapGitHubCmd.Flags().MarkHidden("delete") bootstrapGitHubCmd.Flags().MarkHidden("delete")
bootstrapCmd.AddCommand(bootstrapGitHubCmd) bootstrapCmd.AddCommand(bootstrapGitHubCmd)
@@ -114,30 +117,41 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
repository, err := git.NewRepository(ghRepository, ghOwner, ghHostname, ghToken, "flux", ghOwner+"@users.noreply.github.com") ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if ghSSHHostname != "" { usedPath, bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, rootArgs.namespace, filepath.ToSlash(githubArgs.path.String()))
repository.SSHHost = ghSSHHostname
if bootstrapPathDiffers {
return fmt.Errorf("cluster already bootstrapped to %v path", usedPath)
}
repository, err := git.NewRepository(githubArgs.repository, githubArgs.owner, githubArgs.hostname, ghToken, "flux", githubArgs.owner+"@users.noreply.github.com")
if err != nil {
return err
}
if githubArgs.sshHostname != "" {
repository.SSHHost = githubArgs.sshHostname
} }
provider := &git.GithubProvider{ provider := &git.GithubProvider{
IsPrivate: ghPrivate, IsPrivate: githubArgs.private,
IsPersonal: ghPersonal, IsPersonal: githubArgs.personal,
} }
tmpDir, err := ioutil.TempDir("", namespace) tmpDir, err := ioutil.TempDir("", rootArgs.namespace)
if err != nil { if err != nil {
return err return err
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
ctx, cancel := context.WithTimeout(context.Background(), timeout) if githubArgs.delete {
defer cancel()
if ghDelete {
if err := provider.DeleteRepository(ctx, repository); err != nil { if err := provider.DeleteRepository(ctx, repository); err != nil {
return err return err
} }
@@ -146,7 +160,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
} }
// create GitHub repository if doesn't exists // create GitHub repository if doesn't exists
logger.Actionf("connecting to %s", ghHostname) logger.Actionf("connecting to %s", githubArgs.hostname)
changed, err := provider.CreateRepository(ctx, repository) changed, err := provider.CreateRepository(ctx, repository)
if err != nil { if err != nil {
return err return err
@@ -157,8 +171,8 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
withErrors := false withErrors := false
// add teams to org repository // add teams to org repository
if !ghPersonal { if !githubArgs.personal {
for _, team := range ghTeams { for _, team := range githubArgs.teams {
if changed, err := provider.AddTeam(ctx, repository, team, ghDefaultPermission); err != nil { if changed, err := provider.AddTeam(ctx, repository, team, ghDefaultPermission); err != nil {
logger.Failuref(err.Error()) logger.Failuref(err.Error())
withErrors = true withErrors = true
@@ -169,20 +183,20 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
} }
// clone repository and checkout the main branch // clone repository and checkout the main branch
if err := repository.Checkout(ctx, bootstrapBranch, tmpDir); err != nil { if err := repository.Checkout(ctx, bootstrapArgs.branch, tmpDir); err != nil {
return err return err
} }
logger.Successf("repository cloned") logger.Successf("repository cloned")
// generate install manifests // generate install manifests
logger.Generatef("generating manifests") logger.Generatef("generating manifests")
installManifest, err := generateInstallManifests(ghPath.String(), namespace, tmpDir, bootstrapManifestsPath) installManifest, err := generateInstallManifests(githubArgs.path.String(), rootArgs.namespace, tmpDir, bootstrapArgs.manifestsPath)
if err != nil { if err != nil {
return err return err
} }
// stage install manifests // stage install manifests
changed, err = repository.Commit(ctx, path.Join(ghPath.String(), namespace), "Add manifests") changed, err = repository.Commit(ctx, path.Join(githubArgs.path.String(), rootArgs.namespace), "Add manifests")
if err != nil { if err != nil {
return err return err
} }
@@ -197,17 +211,12 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("components are up to date") logger.Successf("components are up to date")
} }
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
// determine if repo synchronization is working // determine if repo synchronization is working
isInstall := shouldInstallManifests(ctx, kubeClient, namespace) isInstall := shouldInstallManifests(ctx, kubeClient, rootArgs.namespace)
if isInstall { if isInstall {
// apply install manifests // apply install manifests
logger.Actionf("installing components in %s namespace", namespace) logger.Actionf("installing components in %s namespace", rootArgs.namespace)
if err := applyInstallManifests(ctx, installManifest, bootstrapComponents()); err != nil { if err := applyInstallManifests(ctx, installManifest, bootstrapComponents()); err != nil {
return err return err
} }
@@ -216,12 +225,12 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
repoURL := repository.GetURL() repoURL := repository.GetURL()
if bootstrapTokenAuth { if bootstrapArgs.tokenAuth {
// setup HTTPS token auth // setup HTTPS token auth
secret := corev1.Secret{ secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: namespace, Name: rootArgs.namespace,
Namespace: namespace, Namespace: rootArgs.namespace,
}, },
StringData: map[string]string{ StringData: map[string]string{
"username": "git", "username": "git",
@@ -234,21 +243,21 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
} else { } else {
// setup SSH deploy key // setup SSH deploy key
repoURL = repository.GetSSH() repoURL = repository.GetSSH()
if shouldCreateDeployKey(ctx, kubeClient, namespace) { if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
logger.Actionf("configuring deploy key") logger.Actionf("configuring deploy key")
u, err := url.Parse(repository.GetSSH()) u, err := url.Parse(repository.GetSSH())
if err != nil { if err != nil {
return fmt.Errorf("git URL parse failed: %w", err) return fmt.Errorf("git URL parse failed: %w", err)
} }
key, err := generateDeployKey(ctx, kubeClient, u, namespace) key, err := generateDeployKey(ctx, kubeClient, u, rootArgs.namespace)
if err != nil { if err != nil {
return fmt.Errorf("generating deploy key failed: %w", err) return fmt.Errorf("generating deploy key failed: %w", err)
} }
keyName := "flux" keyName := "flux"
if ghPath != "" { if githubArgs.path != "" {
keyName = fmt.Sprintf("flux-%s", ghPath) keyName = fmt.Sprintf("flux-%s", githubArgs.path)
} }
if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil { if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
@@ -261,13 +270,13 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
// configure repo synchronization // configure repo synchronization
logger.Actionf("generating sync manifests") logger.Actionf("generating sync manifests")
syncManifests, err := generateSyncManifests(repoURL, bootstrapBranch, namespace, namespace, ghPath.String(), tmpDir, ghInterval) syncManifests, err := generateSyncManifests(repoURL, bootstrapArgs.branch, rootArgs.namespace, rootArgs.namespace, filepath.ToSlash(githubArgs.path.String()), tmpDir, githubArgs.interval)
if err != nil { if err != nil {
return err return err
} }
// commit and push manifests // commit and push manifests
if changed, err = repository.Commit(ctx, path.Join(ghPath.String(), namespace), "Add manifests"); err != nil { if changed, err = repository.Commit(ctx, path.Join(githubArgs.path.String(), rootArgs.namespace), "Add manifests"); err != nil {
return err return err
} else if changed { } else if changed {
if err := repository.Push(ctx); err != nil { if err := repository.Push(ctx); err != nil {
@@ -278,7 +287,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
// apply manifests and waiting for sync // apply manifests and waiting for sync
logger.Actionf("applying sync manifests") logger.Actionf("applying sync manifests")
if err := applySyncManifests(ctx, kubeClient, namespace, namespace, syncManifests); err != nil { if err := applySyncManifests(ctx, kubeClient, rootArgs.namespace, rootArgs.namespace, syncManifests); err != nil {
return err return err
} }

View File

@@ -23,6 +23,8 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath"
"regexp"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -46,7 +48,7 @@ the bootstrap command will perform an upgrade if needed.`,
Example: ` # Create a GitLab API token and export it as an env var Example: ` # Create a GitLab API token and export it as an env var
export GITLAB_TOKEN=<my-token> export GITLAB_TOKEN=<my-token>
# Run bootstrap for a private repo using HTTPS token authentication # Run bootstrap for a private repo using HTTPS token authentication
flux bootstrap gitlab --owner=<group> --repository=<repo name> --token-auth flux bootstrap gitlab --owner=<group> --repository=<repo name> --token-auth
# Run bootstrap for a private repo using SSH authentication # Run bootstrap for a private repo using SSH authentication
@@ -58,7 +60,7 @@ the bootstrap command will perform an upgrade if needed.`,
# Run bootstrap for a public repository on a personal account # Run bootstrap for a public repository on a personal account
flux bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal --token-auth flux bootstrap gitlab --owner=<user> --repository=<repo name> --private=false --personal --token-auth
# Run bootstrap for a private repo hosted on a GitLab server # Run bootstrap for a private repo hosted on a GitLab server
flux bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain> --token-auth flux bootstrap gitlab --owner=<group> --repository=<repo name> --hostname=<domain> --token-auth
# Run bootstrap for a an existing repository with a branch named main # Run bootstrap for a an existing repository with a branch named main
@@ -67,26 +69,32 @@ the bootstrap command will perform an upgrade if needed.`,
RunE: bootstrapGitLabCmdRun, RunE: bootstrapGitLabCmdRun,
} }
var ( const (
glOwner string gitlabProjectRegex = `\A[[:alnum:]\x{00A9}-\x{1f9ff}_][[:alnum:]\p{Pd}\x{00A9}-\x{1f9ff}_\.]*\z`
glRepository string
glInterval time.Duration
glPersonal bool
glPrivate bool
glHostname string
glSSHHostname string
glPath flags.SafeRelativePath
) )
type gitlabFlags struct {
owner string
repository string
interval time.Duration
personal bool
private bool
hostname string
sshHostname string
path flags.SafeRelativePath
}
var gitlabArgs gitlabFlags
func init() { func init() {
bootstrapGitLabCmd.Flags().StringVar(&glOwner, "owner", "", "GitLab user or group name") bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.owner, "owner", "", "GitLab user or group name")
bootstrapGitLabCmd.Flags().StringVar(&glRepository, "repository", "", "GitLab repository name") bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.repository, "repository", "", "GitLab repository name")
bootstrapGitLabCmd.Flags().BoolVar(&glPersonal, "personal", false, "is personal repository") bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.personal, "personal", false, "is personal repository")
bootstrapGitLabCmd.Flags().BoolVar(&glPrivate, "private", true, "is private repository") bootstrapGitLabCmd.Flags().BoolVar(&gitlabArgs.private, "private", true, "is private repository")
bootstrapGitLabCmd.Flags().DurationVar(&glInterval, "interval", time.Minute, "sync interval") bootstrapGitLabCmd.Flags().DurationVar(&gitlabArgs.interval, "interval", time.Minute, "sync interval")
bootstrapGitLabCmd.Flags().StringVar(&glHostname, "hostname", git.GitLabDefaultHostname, "GitLab hostname") bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.hostname, "hostname", git.GitLabDefaultHostname, "GitLab hostname")
bootstrapGitLabCmd.Flags().StringVar(&glSSHHostname, "ssh-hostname", "", "GitLab SSH hostname, to be used when the SSH host differs from the HTTPS one") bootstrapGitLabCmd.Flags().StringVar(&gitlabArgs.sshHostname, "ssh-hostname", "", "GitLab SSH hostname, to be used when the SSH host differs from the HTTPS one")
bootstrapGitLabCmd.Flags().Var(&glPath, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path") bootstrapGitLabCmd.Flags().Var(&gitlabArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
bootstrapCmd.AddCommand(bootstrapGitLabCmd) bootstrapCmd.AddCommand(bootstrapGitLabCmd)
} }
@@ -97,40 +105,54 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("%s environment variable not found", git.GitLabTokenName) return fmt.Errorf("%s environment variable not found", git.GitLabTokenName)
} }
projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, gitlabArgs.repository)
if err != nil {
return err
}
if !projectNameIsValid {
return fmt.Errorf("%s is an invalid project name for gitlab.\nIt can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.", gitlabArgs.repository)
}
if err := bootstrapValidate(); err != nil { if err := bootstrapValidate(); err != nil {
return err return err
} }
repository, err := git.NewRepository(glRepository, glOwner, glHostname, glToken, "flux", glOwner+"@users.noreply.gitlab.com") ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if glSSHHostname != "" { usedPath, bootstrapPathDiffers := checkIfBootstrapPathDiffers(ctx, kubeClient, rootArgs.namespace, filepath.ToSlash(gitlabArgs.path.String()))
repository.SSHHost = glSSHHostname
if bootstrapPathDiffers {
return fmt.Errorf("cluster already bootstrapped to %v path", usedPath)
} }
provider := &git.GitLabProvider{ repository, err := git.NewRepository(gitlabArgs.repository, gitlabArgs.owner, gitlabArgs.hostname, glToken, "flux", gitlabArgs.owner+"@users.noreply.gitlab.com")
IsPrivate: glPrivate,
IsPersonal: glPersonal,
}
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil { if err != nil {
return err return err
} }
tmpDir, err := ioutil.TempDir("", namespace) if gitlabArgs.sshHostname != "" {
repository.SSHHost = gitlabArgs.sshHostname
}
tmpDir, err := ioutil.TempDir("", rootArgs.namespace)
if err != nil { if err != nil {
return err return err
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
ctx, cancel := context.WithTimeout(context.Background(), timeout) provider := &git.GitLabProvider{
defer cancel() IsPrivate: gitlabArgs.private,
IsPersonal: gitlabArgs.personal,
}
// create GitLab project if doesn't exists // create GitLab project if doesn't exists
logger.Actionf("connecting to %s", glHostname) logger.Actionf("connecting to %s", gitlabArgs.hostname)
changed, err := provider.CreateRepository(ctx, repository) changed, err := provider.CreateRepository(ctx, repository)
if err != nil { if err != nil {
return err return err
@@ -140,20 +162,20 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
} }
// clone repository and checkout the master branch // clone repository and checkout the master branch
if err := repository.Checkout(ctx, bootstrapBranch, tmpDir); err != nil { if err := repository.Checkout(ctx, bootstrapArgs.branch, tmpDir); err != nil {
return err return err
} }
logger.Successf("repository cloned") logger.Successf("repository cloned")
// generate install manifests // generate install manifests
logger.Generatef("generating manifests") logger.Generatef("generating manifests")
installManifest, err := generateInstallManifests(glPath.String(), namespace, tmpDir, bootstrapManifestsPath) installManifest, err := generateInstallManifests(gitlabArgs.path.String(), rootArgs.namespace, tmpDir, bootstrapArgs.manifestsPath)
if err != nil { if err != nil {
return err return err
} }
// stage install manifests // stage install manifests
changed, err = repository.Commit(ctx, path.Join(glPath.String(), namespace), "Add manifests") changed, err = repository.Commit(ctx, path.Join(gitlabArgs.path.String(), rootArgs.namespace), "Add manifests")
if err != nil { if err != nil {
return err return err
} }
@@ -169,11 +191,11 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
} }
// determine if repo synchronization is working // determine if repo synchronization is working
isInstall := shouldInstallManifests(ctx, kubeClient, namespace) isInstall := shouldInstallManifests(ctx, kubeClient, rootArgs.namespace)
if isInstall { if isInstall {
// apply install manifests // apply install manifests
logger.Actionf("installing components in %s namespace", namespace) logger.Actionf("installing components in %s namespace", rootArgs.namespace)
if err := applyInstallManifests(ctx, installManifest, bootstrapComponents()); err != nil { if err := applyInstallManifests(ctx, installManifest, bootstrapComponents()); err != nil {
return err return err
} }
@@ -182,12 +204,12 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
repoURL := repository.GetURL() repoURL := repository.GetURL()
if bootstrapTokenAuth { if bootstrapArgs.tokenAuth {
// setup HTTPS token auth // setup HTTPS token auth
secret := corev1.Secret{ secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: namespace, Name: rootArgs.namespace,
Namespace: namespace, Namespace: rootArgs.namespace,
}, },
StringData: map[string]string{ StringData: map[string]string{
"username": "git", "username": "git",
@@ -200,21 +222,21 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
} else { } else {
// setup SSH deploy key // setup SSH deploy key
repoURL = repository.GetSSH() repoURL = repository.GetSSH()
if shouldCreateDeployKey(ctx, kubeClient, namespace) { if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
logger.Actionf("configuring deploy key") logger.Actionf("configuring deploy key")
u, err := url.Parse(repoURL) u, err := url.Parse(repoURL)
if err != nil { if err != nil {
return fmt.Errorf("git URL parse failed: %w", err) return fmt.Errorf("git URL parse failed: %w", err)
} }
key, err := generateDeployKey(ctx, kubeClient, u, namespace) key, err := generateDeployKey(ctx, kubeClient, u, rootArgs.namespace)
if err != nil { if err != nil {
return fmt.Errorf("generating deploy key failed: %w", err) return fmt.Errorf("generating deploy key failed: %w", err)
} }
keyName := "flux" keyName := "flux"
if glPath != "" { if gitlabArgs.path != "" {
keyName = fmt.Sprintf("flux-%s", glPath) keyName = fmt.Sprintf("flux-%s", gitlabArgs.path)
} }
if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil { if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
@@ -227,13 +249,13 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
// configure repo synchronization // configure repo synchronization
logger.Actionf("generating sync manifests") logger.Actionf("generating sync manifests")
syncManifests, err := generateSyncManifests(repoURL, bootstrapBranch, namespace, namespace, glPath.String(), tmpDir, glInterval) syncManifests, err := generateSyncManifests(repoURL, bootstrapArgs.branch, rootArgs.namespace, rootArgs.namespace, filepath.ToSlash(gitlabArgs.path.String()), tmpDir, gitlabArgs.interval)
if err != nil { if err != nil {
return err return err
} }
// commit and push manifests // commit and push manifests
if changed, err = repository.Commit(ctx, path.Join(glPath.String(), namespace), "Add manifests"); err != nil { if changed, err = repository.Commit(ctx, path.Join(gitlabArgs.path.String(), rootArgs.namespace), "Add manifests"); err != nil {
return err return err
} else if changed { } else if changed {
if err := repository.Push(ctx); err != nil { if err := repository.Push(ctx); err != nil {
@@ -244,7 +266,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
// apply manifests and waiting for sync // apply manifests and waiting for sync
logger.Actionf("applying sync manifests") logger.Actionf("applying sync manifests")
if err := applySyncManifests(ctx, kubeClient, namespace, namespace, syncManifests); err != nil { if err := applySyncManifests(ctx, kubeClient, rootArgs.namespace, rootArgs.namespace, syncManifests); err != nil {
return err return err
} }

View File

@@ -44,25 +44,27 @@ the local environment is configured correctly and if the installed components ar
RunE: runCheckCmd, RunE: runCheckCmd,
} }
var ( type checkFlags struct {
checkPre bool pre bool
checkComponents []string components []string
) }
type kubectlVersion struct { type kubectlVersion struct {
ClientVersion *apimachineryversion.Info `json:"clientVersion"` ClientVersion *apimachineryversion.Info `json:"clientVersion"`
} }
var checkArgs checkFlags
func init() { func init() {
checkCmd.Flags().BoolVarP(&checkPre, "pre", "", false, checkCmd.Flags().BoolVarP(&checkArgs.pre, "pre", "", false,
"only run pre-installation checks") "only run pre-installation checks")
checkCmd.Flags().StringSliceVar(&checkComponents, "components", defaults.Components, checkCmd.Flags().StringSliceVar(&checkArgs.components, "components", rootArgs.defaults.Components,
"list of components, accepts comma-separated values") "list of components, accepts comma-separated values")
rootCmd.AddCommand(checkCmd) rootCmd.AddCommand(checkCmd)
} }
func runCheckCmd(cmd *cobra.Command, args []string) error { func runCheckCmd(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
logger.Actionf("checking prerequisites") logger.Actionf("checking prerequisites")
@@ -76,7 +78,7 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
checkFailed = true checkFailed = true
} }
if checkPre { if checkArgs.pre {
if checkFailed { if checkFailed {
os.Exit(1) os.Exit(1)
} }
@@ -103,7 +105,7 @@ func kubectlCheck(ctx context.Context, version string) bool {
} }
kubectlArgs := []string{"version", "--client", "--output", "json"} kubectlArgs := []string{"version", "--client", "--output", "json"}
output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, kubeconfig, kubecontext, kubectlArgs...) output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
if err != nil { if err != nil {
logger.Failuref("kubectl version can't be determined") logger.Failuref("kubectl version can't be determined")
return false return false
@@ -132,7 +134,7 @@ func kubectlCheck(ctx context.Context, version string) bool {
} }
func kubernetesCheck(version string) bool { func kubernetesCheck(version string) bool {
cfg, err := utils.KubeConfig(kubeconfig, kubecontext) cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
logger.Failuref("Kubernetes client initialization failed: %s", err.Error()) logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
return false return false
@@ -167,20 +169,20 @@ func kubernetesCheck(version string) bool {
} }
func componentsCheck() bool { func componentsCheck() bool {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
ok := true ok := true
for _, deployment := range checkComponents { for _, deployment := range checkArgs.components {
kubectlArgs := []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()} kubectlArgs := []string{"-n", rootArgs.namespace, "rollout", "status", "deployment", deployment, "--timeout", rootArgs.timeout.String()}
if output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, kubeconfig, kubecontext, kubectlArgs...); err != nil { if output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
logger.Failuref("%s: %s", deployment, strings.TrimSuffix(output, "\n")) logger.Failuref("%s: %s", deployment, strings.TrimSuffix(output, "\n"))
ok = false ok = false
} else { } else {
logger.Successf("%s is healthy", deployment) logger.Successf("%s is healthy", deployment)
} }
kubectlArgs = []string{"-n", namespace, "get", "deployment", deployment, "-o", "jsonpath=\"{..image}\""} kubectlArgs = []string{"-n", rootArgs.namespace, "get", "deployment", deployment, "-o", "jsonpath=\"{..image}\""}
if output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, kubeconfig, kubecontext, kubectlArgs...); err == nil { if output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err == nil {
logger.Actionf(strings.TrimPrefix(strings.TrimSuffix(output, "\""), "\"")) logger.Actionf(strings.TrimPrefix(strings.TrimSuffix(output, "\""), "\""))
} }
} }

View File

@@ -38,16 +38,18 @@ var createCmd = &cobra.Command{
Long: "The create sub-commands generate sources and resources.", Long: "The create sub-commands generate sources and resources.",
} }
var ( type createFlags struct {
interval time.Duration interval time.Duration
export bool export bool
labels []string labels []string
) }
var createArgs createFlags
func init() { func init() {
createCmd.PersistentFlags().DurationVarP(&interval, "interval", "", time.Minute, "source sync interval") createCmd.PersistentFlags().DurationVarP(&createArgs.interval, "interval", "", time.Minute, "source sync interval")
createCmd.PersistentFlags().BoolVar(&export, "export", false, "export in YAML format to stdout") createCmd.PersistentFlags().BoolVar(&createArgs.export, "export", false, "export in YAML format to stdout")
createCmd.PersistentFlags().StringSliceVar(&labels, "label", nil, createCmd.PersistentFlags().StringSliceVar(&createArgs.labels, "label", nil,
"set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)") "set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)")
rootCmd.AddCommand(createCmd) rootCmd.AddCommand(createCmd)
} }
@@ -76,7 +78,7 @@ func (names apiType) upsert(ctx context.Context, kubeClient client.Client, objec
Name: object.GetName(), Name: object.GetName(),
} }
op, err := controllerutil.CreateOrUpdate(ctx, kubeClient, object.asRuntimeObject(), mutate) op, err := controllerutil.CreateOrUpdate(ctx, kubeClient, object.asClientObject(), mutate)
if err != nil { if err != nil {
return nsname, err return nsname, err
} }
@@ -99,10 +101,10 @@ type upsertWaitable interface {
// resource, then waiting for it to reconcile. See the note on // resource, then waiting for it to reconcile. See the note on
// `upsert` for how to work with the `mutate` argument. // `upsert` for how to work with the `mutate` argument.
func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) error { func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) // NB globals kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) // NB globals
if err != nil { if err != nil {
return err return err
} }
@@ -116,7 +118,7 @@ func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) e
} }
logger.Waitingf("waiting for %s reconciliation", names.kind) logger.Waitingf("waiting for %s reconciliation", names.kind)
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReady(ctx, kubeClient, namespacedName, object)); err != nil { isReady(ctx, kubeClient, namespacedName, object)); err != nil {
return err return err
} }
@@ -126,7 +128,7 @@ func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) e
func parseLabels() (map[string]string, error) { func parseLabels() (map[string]string, error) {
result := make(map[string]string) result := make(map[string]string)
for _, label := range labels { for _, label := range createArgs.labels {
// validate key value pair // validate key value pair
parts := strings.Split(label, "=") parts := strings.Split(label, "=")
if len(parts) != 2 { if len(parts) != 2 {

View File

@@ -20,11 +20,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
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"
@@ -33,6 +29,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/utils"
) )
var createAlertCmd = &cobra.Command{ var createAlertCmd = &cobra.Command{
@@ -49,16 +48,18 @@ var createAlertCmd = &cobra.Command{
RunE: createAlertCmdRun, RunE: createAlertCmdRun,
} }
var ( type alertFlags struct {
aProviderRef string providerRef string
aEventSeverity string eventSeverity string
aEventSources []string eventSources []string
) }
var alertArgs alertFlags
func init() { func init() {
createAlertCmd.Flags().StringVar(&aProviderRef, "provider-ref", "", "reference to provider") createAlertCmd.Flags().StringVar(&alertArgs.providerRef, "provider-ref", "", "reference to provider")
createAlertCmd.Flags().StringVar(&aEventSeverity, "event-severity", "", "severity of events to send alerts for") createAlertCmd.Flags().StringVar(&alertArgs.eventSeverity, "event-severity", "", "severity of events to send alerts for")
createAlertCmd.Flags().StringArrayVar(&aEventSources, "event-source", []string{}, "sources that should generate alerts (<kind>/<name>)") createAlertCmd.Flags().StringArrayVar(&alertArgs.eventSources, "event-source", []string{}, "sources that should generate alerts (<kind>/<name>)")
createCmd.AddCommand(createAlertCmd) createCmd.AddCommand(createAlertCmd)
} }
@@ -68,12 +69,12 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
if aProviderRef == "" { if alertArgs.providerRef == "" {
return fmt.Errorf("provider ref is required") return fmt.Errorf("provider ref is required")
} }
eventSources := []notificationv1.CrossNamespaceObjectReference{} eventSources := []notificationv1.CrossNamespaceObjectReference{}
for _, eventSource := range aEventSources { for _, eventSource := range alertArgs.eventSources {
kind, name := utils.ParseObjectKindName(eventSource) kind, name := utils.ParseObjectKindName(eventSource)
if kind == "" { if kind == "" {
return fmt.Errorf("invalid event source '%s', must be in format <kind>/<name>", eventSource) return fmt.Errorf("invalid event source '%s', must be in format <kind>/<name>", eventSource)
@@ -94,34 +95,34 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !export { if !createArgs.export {
logger.Generatef("generating Alert") logger.Generatef("generating Alert")
} }
alert := notificationv1.Alert{ alert := notificationv1.Alert{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
Spec: notificationv1.AlertSpec{ Spec: notificationv1.AlertSpec{
ProviderRef: corev1.LocalObjectReference{ ProviderRef: meta.LocalObjectReference{
Name: aProviderRef, Name: alertArgs.providerRef,
}, },
EventSeverity: aEventSeverity, EventSeverity: alertArgs.eventSeverity,
EventSources: eventSources, EventSources: eventSources,
Suspend: false, Suspend: false,
}, },
} }
if export { if createArgs.export {
return exportAlert(alert) return exportAlert(alert)
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
@@ -133,7 +134,7 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
} }
logger.Waitingf("waiting for Alert reconciliation") logger.Waitingf("waiting for Alert reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil { isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err return err
} }

View File

@@ -21,7 +21,6 @@ import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
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"
@@ -29,9 +28,10 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/utils"
) )
var createAlertProviderCmd = &cobra.Command{ var createAlertProviderCmd = &cobra.Command{
@@ -54,20 +54,22 @@ var createAlertProviderCmd = &cobra.Command{
RunE: createAlertProviderCmdRun, RunE: createAlertProviderCmdRun,
} }
var ( type alertProviderFlags struct {
apType string alertType string
apChannel string channel string
apUsername string username string
apAddress string address string
apSecretRef string secretRef string
) }
var alertProviderArgs alertProviderFlags
func init() { func init() {
createAlertProviderCmd.Flags().StringVar(&apType, "type", "", "type of provider") createAlertProviderCmd.Flags().StringVar(&alertProviderArgs.alertType, "type", "", "type of provider")
createAlertProviderCmd.Flags().StringVar(&apChannel, "channel", "", "channel to send messages to in the case of a chat provider") createAlertProviderCmd.Flags().StringVar(&alertProviderArgs.channel, "channel", "", "channel to send messages to in the case of a chat provider")
createAlertProviderCmd.Flags().StringVar(&apUsername, "username", "", "bot username used by the provider") createAlertProviderCmd.Flags().StringVar(&alertProviderArgs.username, "username", "", "bot username used by the provider")
createAlertProviderCmd.Flags().StringVar(&apAddress, "address", "", "path to either the git repository, chat provider or webhook") createAlertProviderCmd.Flags().StringVar(&alertProviderArgs.address, "address", "", "path to either the git repository, chat provider or webhook")
createAlertProviderCmd.Flags().StringVar(&apSecretRef, "secret-ref", "", "name of secret containing authentication token") createAlertProviderCmd.Flags().StringVar(&alertProviderArgs.secretRef, "secret-ref", "", "name of secret containing authentication token")
createCmd.AddCommand(createAlertProviderCmd) createCmd.AddCommand(createAlertProviderCmd)
} }
@@ -77,7 +79,7 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
if apType == "" { if alertProviderArgs.alertType == "" {
return fmt.Errorf("Provider type is required") return fmt.Errorf("Provider type is required")
} }
@@ -86,38 +88,38 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !export { if !createArgs.export {
logger.Generatef("generating Provider") logger.Generatef("generating Provider")
} }
provider := notificationv1.Provider{ provider := notificationv1.Provider{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
Spec: notificationv1.ProviderSpec{ Spec: notificationv1.ProviderSpec{
Type: apType, Type: alertProviderArgs.alertType,
Channel: apChannel, Channel: alertProviderArgs.channel,
Username: apUsername, Username: alertProviderArgs.username,
Address: apAddress, Address: alertProviderArgs.address,
}, },
} }
if apSecretRef != "" { if alertProviderArgs.secretRef != "" {
provider.Spec.SecretRef = &corev1.LocalObjectReference{ provider.Spec.SecretRef = &meta.LocalObjectReference{
Name: apSecretRef, Name: alertProviderArgs.secretRef,
} }
} }
if export { if createArgs.export {
return exportAlertProvider(provider) return exportAlertProvider(provider)
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
@@ -129,7 +131,7 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
} }
logger.Waitingf("waiting for Provider reconciliation") logger.Waitingf("waiting for Provider reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isAlertProviderReady(ctx, kubeClient, namespacedName, &provider)); err != nil { isAlertProviderReady(ctx, kubeClient, namespacedName, &provider)); err != nil {
return err return err
} }

View File

@@ -98,28 +98,30 @@ var createHelmReleaseCmd = &cobra.Command{
RunE: createHelmReleaseCmdRun, RunE: createHelmReleaseCmdRun,
} }
var ( type helmReleaseFlags struct {
hrName string name string
hrSource flags.HelmChartSource source flags.HelmChartSource
hrDependsOn []string dependsOn []string
hrChart string chart string
hrChartVersion string chartVersion string
hrTargetNamespace string targetNamespace string
hrValuesFile string valuesFile string
hrValuesFrom flags.HelmReleaseValuesFrom valuesFrom flags.HelmReleaseValuesFrom
hrSAName string saName string
) }
var helmReleaseArgs helmReleaseFlags
func init() { func init() {
createHelmReleaseCmd.Flags().StringVar(&hrName, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.name, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'")
createHelmReleaseCmd.Flags().Var(&hrSource, "source", hrSource.Description()) createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.source, "source", helmReleaseArgs.source.Description())
createHelmReleaseCmd.Flags().StringVar(&hrChart, "chart", "", "Helm chart name or path") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.chart, "chart", "", "Helm chart name or path")
createHelmReleaseCmd.Flags().StringVar(&hrChartVersion, "chart-version", "", "Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.chartVersion, "chart-version", "", "Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)")
createHelmReleaseCmd.Flags().StringArrayVar(&hrDependsOn, "depends-on", nil, "HelmReleases that must be ready before this release can be installed, supported formats '<name>' and '<namespace>/<name>'") createHelmReleaseCmd.Flags().StringArrayVar(&helmReleaseArgs.dependsOn, "depends-on", nil, "HelmReleases that must be ready before this release can be installed, supported formats '<name>' and '<namespace>/<name>'")
createHelmReleaseCmd.Flags().StringVar(&hrTargetNamespace, "target-namespace", "", "namespace to install this release, defaults to the HelmRelease namespace") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.targetNamespace, "target-namespace", "", "namespace to install this release, defaults to the HelmRelease namespace")
createHelmReleaseCmd.Flags().StringVar(&hrSAName, "service-account", "", "the name of the service account to impersonate when reconciling this HelmRelease") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.saName, "service-account", "", "the name of the service account to impersonate when reconciling this HelmRelease")
createHelmReleaseCmd.Flags().StringVar(&hrValuesFile, "values", "", "local path to the values.yaml file") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.valuesFile, "values", "", "local path to the values.yaml file")
createHelmReleaseCmd.Flags().Var(&hrValuesFrom, "values-from", hrValuesFrom.Description()) createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.valuesFrom, "values-from", helmReleaseArgs.valuesFrom.Description())
createCmd.AddCommand(createHelmReleaseCmd) createCmd.AddCommand(createHelmReleaseCmd)
} }
@@ -129,7 +131,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
if hrChart == "" { if helmReleaseArgs.chart == "" {
return fmt.Errorf("chart name or path is required") return fmt.Errorf("chart name or path is required")
} }
@@ -138,30 +140,30 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !export { if !createArgs.export {
logger.Generatef("generating HelmRelease") logger.Generatef("generating HelmRelease")
} }
helmRelease := helmv2.HelmRelease{ helmRelease := helmv2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
Spec: helmv2.HelmReleaseSpec{ Spec: helmv2.HelmReleaseSpec{
ReleaseName: hrName, ReleaseName: helmReleaseArgs.name,
DependsOn: utils.MakeDependsOn(hrDependsOn), DependsOn: utils.MakeDependsOn(helmReleaseArgs.dependsOn),
Interval: metav1.Duration{ Interval: metav1.Duration{
Duration: interval, Duration: createArgs.interval,
}, },
TargetNamespace: hrTargetNamespace, TargetNamespace: helmReleaseArgs.targetNamespace,
Chart: helmv2.HelmChartTemplate{ Chart: helmv2.HelmChartTemplate{
Spec: helmv2.HelmChartTemplateSpec{ Spec: helmv2.HelmChartTemplateSpec{
Chart: hrChart, Chart: helmReleaseArgs.chart,
Version: hrChartVersion, Version: helmReleaseArgs.chartVersion,
SourceRef: helmv2.CrossNamespaceObjectReference{ SourceRef: helmv2.CrossNamespaceObjectReference{
Kind: hrSource.Kind, Kind: helmReleaseArgs.source.Kind,
Name: hrSource.Name, Name: helmReleaseArgs.source.Name,
}, },
}, },
}, },
@@ -169,39 +171,39 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}, },
} }
if hrSAName != "" { if helmReleaseArgs.saName != "" {
helmRelease.Spec.ServiceAccountName = hrSAName helmRelease.Spec.ServiceAccountName = helmReleaseArgs.saName
} }
if hrValuesFile != "" { if helmReleaseArgs.valuesFile != "" {
data, err := ioutil.ReadFile(hrValuesFile) data, err := ioutil.ReadFile(helmReleaseArgs.valuesFile)
if err != nil { if err != nil {
return fmt.Errorf("reading values from %s failed: %w", hrValuesFile, err) return fmt.Errorf("reading values from %s failed: %w", helmReleaseArgs.valuesFile, err)
} }
json, err := yaml.YAMLToJSON(data) json, err := yaml.YAMLToJSON(data)
if err != nil { if err != nil {
return fmt.Errorf("converting values to JSON from %s failed: %w", hrValuesFile, err) return fmt.Errorf("converting values to JSON from %s failed: %w", helmReleaseArgs.valuesFile, err)
} }
helmRelease.Spec.Values = &apiextensionsv1.JSON{Raw: json} helmRelease.Spec.Values = &apiextensionsv1.JSON{Raw: json}
} }
if hrValuesFrom.String() != "" { if helmReleaseArgs.valuesFrom.String() != "" {
helmRelease.Spec.ValuesFrom = []helmv2.ValuesReference{{ helmRelease.Spec.ValuesFrom = []helmv2.ValuesReference{{
Kind: hrValuesFrom.Kind, Kind: helmReleaseArgs.valuesFrom.Kind,
Name: hrValuesFrom.Name, Name: helmReleaseArgs.valuesFrom.Name,
}} }}
} }
if export { if createArgs.export {
return exportHelmRelease(helmRelease) return exportHelmRelease(helmRelease)
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
@@ -213,7 +215,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
} }
logger.Waitingf("waiting for HelmRelease reconciliation") logger.Waitingf("waiting for HelmRelease reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isHelmReleaseReady(ctx, kubeClient, namespacedName, &helmRelease)); err != nil { isHelmReleaseReady(ctx, kubeClient, namespacedName, &helmRelease)); err != nil {
return err return err
} }

View File

@@ -20,9 +20,10 @@ import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/apis/meta"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
) )
@@ -38,8 +39,9 @@ the status of the object.`,
RunE: createImagePolicyRun} RunE: createImagePolicyRun}
type imagePolicyFlags struct { type imagePolicyFlags struct {
imageRef string imageRef string
semver string semver string
filterRegex string
} }
var imagePolicyArgs = imagePolicyFlags{} var imagePolicyArgs = imagePolicyFlags{}
@@ -48,6 +50,7 @@ func init() {
flags := createImagePolicyCmd.Flags() flags := createImagePolicyCmd.Flags()
flags.StringVar(&imagePolicyArgs.imageRef, "image-ref", "", "the name of an image repository object") flags.StringVar(&imagePolicyArgs.imageRef, "image-ref", "", "the name of an image repository object")
flags.StringVar(&imagePolicyArgs.semver, "semver", "", "a semver range to apply to tags; e.g., '1.x'") flags.StringVar(&imagePolicyArgs.semver, "semver", "", "a semver range to apply to tags; e.g., '1.x'")
flags.StringVar(&imagePolicyArgs.filterRegex, "filter-regex", "", " regular expression pattern used to filter the image tags")
createImageCmd.AddCommand(createImagePolicyCmd) createImageCmd.AddCommand(createImagePolicyCmd)
} }
@@ -76,11 +79,11 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
var policy = imagev1.ImagePolicy{ var policy = imagev1.ImagePolicy{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: objectName, Name: objectName,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: labels, Labels: labels,
}, },
Spec: imagev1.ImagePolicySpec{ Spec: imagev1.ImagePolicySpec{
ImageRepositoryRef: corev1.LocalObjectReference{ ImageRepositoryRef: meta.LocalObjectReference{
Name: imagePolicyArgs.imageRef, Name: imagePolicyArgs.imageRef,
}, },
}, },
@@ -95,7 +98,13 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("a policy must be provided with --semver") return fmt.Errorf("a policy must be provided with --semver")
} }
if export { if imagePolicyArgs.filterRegex != "" {
policy.Spec.FilterTags = &imagev1.TagFilter{
Pattern: imagePolicyArgs.filterRegex,
}
}
if createArgs.export {
return printExport(exportImagePolicy(&policy)) return printExport(exportImagePolicy(&policy))
} }

View File

@@ -22,9 +22,10 @@ import (
"github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/name"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/apis/meta"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
) )
@@ -77,24 +78,24 @@ func createImageRepositoryRun(cmd *cobra.Command, args []string) error {
var repo = imagev1.ImageRepository{ var repo = imagev1.ImageRepository{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: objectName, Name: objectName,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: labels, Labels: labels,
}, },
Spec: imagev1.ImageRepositorySpec{ Spec: imagev1.ImageRepositorySpec{
Image: imageRepoArgs.image, Image: imageRepoArgs.image,
Interval: metav1.Duration{Duration: interval}, Interval: metav1.Duration{Duration: createArgs.interval},
}, },
} }
if imageRepoArgs.timeout != 0 { if imageRepoArgs.timeout != 0 {
repo.Spec.Timeout = &metav1.Duration{Duration: imageRepoArgs.timeout} repo.Spec.Timeout = &metav1.Duration{Duration: imageRepoArgs.timeout}
} }
if imageRepoArgs.secretRef != "" { if imageRepoArgs.secretRef != "" {
repo.Spec.SecretRef = &corev1.LocalObjectReference{ repo.Spec.SecretRef = &meta.LocalObjectReference{
Name: imageRepoArgs.secretRef, Name: imageRepoArgs.secretRef,
} }
} }
if export { if createArgs.export {
return printExport(exportImageRepository(&repo)) return printExport(exportImageRepository(&repo))
} }

View File

@@ -20,9 +20,10 @@ import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/apis/meta"
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1" autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
) )
@@ -50,7 +51,7 @@ var imageUpdateArgs = imageUpdateFlags{}
func init() { func init() {
flags := createImageUpdateCmd.Flags() flags := createImageUpdateCmd.Flags()
flags.StringVar(&imageUpdateArgs.gitRepoRef, "git-repo-ref", "", "the name of a GitRepository resource with details of the upstream git repository") flags.StringVar(&imageUpdateArgs.gitRepoRef, "git-repo-ref", "", "the name of a GitRepository resource with details of the upstream git repository")
flags.StringVar(&imageUpdateArgs.branch, "branch", "", "the branch to push commits to") flags.StringVar(&imageUpdateArgs.branch, "branch", "", "the branch to checkout and push commits to")
flags.StringVar(&imageUpdateArgs.commitTemplate, "commit-template", "", "a template for commit messages") flags.StringVar(&imageUpdateArgs.commitTemplate, "commit-template", "", "a template for commit messages")
flags.StringVar(&imageUpdateArgs.authorName, "author-name", "", "the name to use for commit author") flags.StringVar(&imageUpdateArgs.authorName, "author-name", "", "the name to use for commit author")
flags.StringVar(&imageUpdateArgs.authorEmail, "author-email", "", "the email to use for commit author") flags.StringVar(&imageUpdateArgs.authorEmail, "author-email", "", "the email to use for commit author")
@@ -68,6 +69,10 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("a reference to a GitRepository is required (--git-repo-ref)") return fmt.Errorf("a reference to a GitRepository is required (--git-repo-ref)")
} }
if imageUpdateArgs.branch == "" {
return fmt.Errorf("the Git repoistory branch is required (--branch)")
}
labels, err := parseLabels() labels, err := parseLabels()
if err != nil { if err != nil {
return err return err
@@ -76,20 +81,17 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
var update = autov1.ImageUpdateAutomation{ var update = autov1.ImageUpdateAutomation{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: objectName, Name: objectName,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: labels, Labels: labels,
}, },
Spec: autov1.ImageUpdateAutomationSpec{ Spec: autov1.ImageUpdateAutomationSpec{
Checkout: autov1.GitCheckoutSpec{ Checkout: autov1.GitCheckoutSpec{
GitRepositoryRef: corev1.LocalObjectReference{ GitRepositoryRef: meta.LocalObjectReference{
Name: imageUpdateArgs.gitRepoRef, Name: imageUpdateArgs.gitRepoRef,
}, },
Branch: imageUpdateArgs.branch, Branch: imageUpdateArgs.branch,
}, },
Interval: metav1.Duration{Duration: interval}, Interval: metav1.Duration{Duration: createArgs.interval},
Update: autov1.UpdateStrategy{
Setters: &autov1.SettersStrategy{},
},
Commit: autov1.CommitSpec{ Commit: autov1.CommitSpec{
AuthorName: imageUpdateArgs.authorName, AuthorName: imageUpdateArgs.authorName,
AuthorEmail: imageUpdateArgs.authorEmail, AuthorEmail: imageUpdateArgs.authorEmail,
@@ -98,7 +100,7 @@ func createImageUpdateRun(cmd *cobra.Command, args []string) error {
}, },
} }
if export { if createArgs.export {
return printExport(exportImageUpdate(&update)) return printExport(exportImageUpdate(&update))
} }

View File

@@ -23,7 +23,6 @@ import (
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
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"
@@ -31,11 +30,12 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
) )
var createKsCmd = &cobra.Command{ var createKsCmd = &cobra.Command{
@@ -72,53 +72,61 @@ var createKsCmd = &cobra.Command{
RunE: createKsCmdRun, RunE: createKsCmdRun,
} }
var ( type kustomizationFlags struct {
ksSource flags.KustomizationSource source flags.KustomizationSource
ksPath flags.SafeRelativePath = "./" path flags.SafeRelativePath
ksPrune bool prune bool
ksDependsOn []string dependsOn []string
ksValidation string validation string
ksHealthCheck []string healthCheck []string
ksHealthTimeout time.Duration healthTimeout time.Duration
ksSAName string saName string
ksDecryptionProvider flags.DecryptionProvider decryptionProvider flags.DecryptionProvider
ksDecryptionSecret string decryptionSecret string
ksTargetNamespace string targetNamespace string
) }
var kustomizationArgs = NewKustomizationFlags()
func init() { func init() {
createKsCmd.Flags().Var(&ksSource, "source", ksSource.Description()) createKsCmd.Flags().Var(&kustomizationArgs.source, "source", kustomizationArgs.source.Description())
createKsCmd.Flags().Var(&ksPath, "path", "path to the directory containing a kustomization.yaml file") createKsCmd.Flags().Var(&kustomizationArgs.path, "path", "path to the directory containing a kustomization.yaml file")
createKsCmd.Flags().BoolVar(&ksPrune, "prune", false, "enable garbage collection") createKsCmd.Flags().BoolVar(&kustomizationArgs.prune, "prune", false, "enable garbage collection")
createKsCmd.Flags().StringArrayVar(&ksHealthCheck, "health-check", nil, "workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'") createKsCmd.Flags().StringArrayVar(&kustomizationArgs.healthCheck, "health-check", nil, "workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'")
createKsCmd.Flags().DurationVar(&ksHealthTimeout, "health-check-timeout", 2*time.Minute, "timeout of health checking operations") createKsCmd.Flags().DurationVar(&kustomizationArgs.healthTimeout, "health-check-timeout", 2*time.Minute, "timeout of health checking operations")
createKsCmd.Flags().StringVar(&ksValidation, "validation", "", "validate the manifests before applying them on the cluster, can be 'client' or 'server'") createKsCmd.Flags().StringVar(&kustomizationArgs.validation, "validation", "", "validate the manifests before applying them on the cluster, can be 'client' or 'server'")
createKsCmd.Flags().StringArrayVar(&ksDependsOn, "depends-on", nil, "Kustomization that must be ready before this Kustomization can be applied, supported formats '<name>' and '<namespace>/<name>'") createKsCmd.Flags().StringArrayVar(&kustomizationArgs.dependsOn, "depends-on", nil, "Kustomization that must be ready before this Kustomization can be applied, supported formats '<name>' and '<namespace>/<name>'")
createKsCmd.Flags().StringVar(&ksSAName, "service-account", "", "the name of the service account to impersonate when reconciling this Kustomization") createKsCmd.Flags().StringVar(&kustomizationArgs.saName, "service-account", "", "the name of the service account to impersonate when reconciling this Kustomization")
createKsCmd.Flags().Var(&ksDecryptionProvider, "decryption-provider", ksDecryptionProvider.Description()) createKsCmd.Flags().Var(&kustomizationArgs.decryptionProvider, "decryption-provider", kustomizationArgs.decryptionProvider.Description())
createKsCmd.Flags().StringVar(&ksDecryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption") createKsCmd.Flags().StringVar(&kustomizationArgs.decryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption")
createKsCmd.Flags().StringVar(&ksTargetNamespace, "target-namespace", "", "overrides the namespace of all Kustomization objects reconciled by this Kustomization") createKsCmd.Flags().StringVar(&kustomizationArgs.targetNamespace, "target-namespace", "", "overrides the namespace of all Kustomization objects reconciled by this Kustomization")
createCmd.AddCommand(createKsCmd) createCmd.AddCommand(createKsCmd)
} }
func NewKustomizationFlags() kustomizationFlags {
return kustomizationFlags{
path: "./",
}
}
func createKsCmdRun(cmd *cobra.Command, args []string) error { func createKsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("Kustomization name is required") return fmt.Errorf("Kustomization name is required")
} }
name := args[0] name := args[0]
if ksPath == "" { if kustomizationArgs.path == "" {
return fmt.Errorf("path is required") return fmt.Errorf("path is required")
} }
if !strings.HasPrefix(ksPath.String(), "./") { if !strings.HasPrefix(kustomizationArgs.path.String(), "./") {
return fmt.Errorf("path must begin with ./") return fmt.Errorf("path must begin with ./")
} }
if !export { if !createArgs.export {
logger.Generatef("generating Kustomization") logger.Generatef("generating Kustomization")
} }
ksLabels, err := parseLabels() kslabels, err := parseLabels()
if err != nil { if err != nil {
return err return err
} }
@@ -126,29 +134,29 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
kustomization := kustomizev1.Kustomization{ kustomization := kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: ksLabels, Labels: kslabels,
}, },
Spec: kustomizev1.KustomizationSpec{ Spec: kustomizev1.KustomizationSpec{
DependsOn: utils.MakeDependsOn(ksDependsOn), DependsOn: utils.MakeDependsOn(kustomizationArgs.dependsOn),
Interval: metav1.Duration{ Interval: metav1.Duration{
Duration: interval, Duration: createArgs.interval,
}, },
Path: ksPath.String(), Path: kustomizationArgs.path.String(),
Prune: ksPrune, Prune: kustomizationArgs.prune,
SourceRef: kustomizev1.CrossNamespaceSourceReference{ SourceRef: kustomizev1.CrossNamespaceSourceReference{
Kind: ksSource.Kind, Kind: kustomizationArgs.source.Kind,
Name: ksSource.Name, Name: kustomizationArgs.source.Name,
}, },
Suspend: false, Suspend: false,
Validation: ksValidation, Validation: kustomizationArgs.validation,
TargetNamespace: ksTargetNamespace, TargetNamespace: kustomizationArgs.targetNamespace,
}, },
} }
if len(ksHealthCheck) > 0 { if len(kustomizationArgs.healthCheck) > 0 {
healthChecks := make([]kustomizev1.CrossNamespaceObjectReference, 0) healthChecks := make([]meta.NamespacedObjectKindReference, 0)
for _, w := range ksHealthCheck { for _, w := range kustomizationArgs.healthCheck {
kindObj := strings.Split(w, "/") kindObj := strings.Split(w, "/")
if len(kindObj) != 2 { if len(kindObj) != 2 {
return fmt.Errorf("invalid health check '%s' must be in the format 'kind/name.namespace' %v", w, kindObj) return fmt.Errorf("invalid health check '%s' must be in the format 'kind/name.namespace' %v", w, kindObj)
@@ -170,7 +178,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid health check '%s' must be in the format 'kind/name.namespace'", w) return fmt.Errorf("invalid health check '%s' must be in the format 'kind/name.namespace'", w)
} }
check := kustomizev1.CrossNamespaceObjectReference{ check := meta.NamespacedObjectKindReference{
Kind: kind, Kind: kind,
Name: nameNs[0], Name: nameNs[0],
Namespace: nameNs[1], Namespace: nameNs[1],
@@ -183,32 +191,32 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
} }
kustomization.Spec.HealthChecks = healthChecks kustomization.Spec.HealthChecks = healthChecks
kustomization.Spec.Timeout = &metav1.Duration{ kustomization.Spec.Timeout = &metav1.Duration{
Duration: ksHealthTimeout, Duration: kustomizationArgs.healthTimeout,
} }
} }
if ksSAName != "" { if kustomizationArgs.saName != "" {
kustomization.Spec.ServiceAccountName = ksSAName kustomization.Spec.ServiceAccountName = kustomizationArgs.saName
} }
if ksDecryptionProvider != "" { if kustomizationArgs.decryptionProvider != "" {
kustomization.Spec.Decryption = &kustomizev1.Decryption{ kustomization.Spec.Decryption = &kustomizev1.Decryption{
Provider: ksDecryptionProvider.String(), Provider: kustomizationArgs.decryptionProvider.String(),
} }
if ksDecryptionSecret != "" { if kustomizationArgs.decryptionSecret != "" {
kustomization.Spec.Decryption.SecretRef = &corev1.LocalObjectReference{Name: ksDecryptionSecret} kustomization.Spec.Decryption.SecretRef = &meta.LocalObjectReference{Name: kustomizationArgs.decryptionSecret}
} }
} }
if export { if createArgs.export {
return exportKs(kustomization) return exportKs(kustomization)
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
@@ -220,7 +228,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
} }
logger.Waitingf("waiting for Kustomization reconciliation") logger.Waitingf("waiting for Kustomization reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isKustomizationReady(ctx, kubeClient, namespacedName, &kustomization)); err != nil { isKustomizationReady(ctx, kubeClient, namespacedName, &kustomization)); err != nil {
return err return err
} }

View File

@@ -21,7 +21,6 @@ import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
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"
@@ -29,9 +28,10 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils"
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/utils"
) )
var createReceiverCmd = &cobra.Command{ var createReceiverCmd = &cobra.Command{
@@ -50,18 +50,20 @@ var createReceiverCmd = &cobra.Command{
RunE: createReceiverCmdRun, RunE: createReceiverCmdRun,
} }
var ( type receiverFlags struct {
rcvType string receiverType string
rcvSecretRef string secretRef string
rcvEvents []string events []string
rcvResources []string resources []string
) }
var receiverArgs receiverFlags
func init() { func init() {
createReceiverCmd.Flags().StringVar(&rcvType, "type", "", "") createReceiverCmd.Flags().StringVar(&receiverArgs.receiverType, "type", "", "")
createReceiverCmd.Flags().StringVar(&rcvSecretRef, "secret-ref", "", "") createReceiverCmd.Flags().StringVar(&receiverArgs.secretRef, "secret-ref", "", "")
createReceiverCmd.Flags().StringArrayVar(&rcvEvents, "event", []string{}, "") createReceiverCmd.Flags().StringArrayVar(&receiverArgs.events, "event", []string{}, "")
createReceiverCmd.Flags().StringArrayVar(&rcvResources, "resource", []string{}, "") createReceiverCmd.Flags().StringArrayVar(&receiverArgs.resources, "resource", []string{}, "")
createCmd.AddCommand(createReceiverCmd) createCmd.AddCommand(createReceiverCmd)
} }
@@ -71,16 +73,16 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
if rcvType == "" { if receiverArgs.receiverType == "" {
return fmt.Errorf("Receiver type is required") return fmt.Errorf("Receiver type is required")
} }
if rcvSecretRef == "" { if receiverArgs.secretRef == "" {
return fmt.Errorf("secret ref is required") return fmt.Errorf("secret ref is required")
} }
resources := []notificationv1.CrossNamespaceObjectReference{} resources := []notificationv1.CrossNamespaceObjectReference{}
for _, resource := range rcvResources { for _, resource := range receiverArgs.resources {
kind, name := utils.ParseObjectKindName(resource) kind, name := utils.ParseObjectKindName(resource)
if kind == "" { if kind == "" {
return fmt.Errorf("invalid event source '%s', must be in format <kind>/<name>", resource) return fmt.Errorf("invalid event source '%s', must be in format <kind>/<name>", resource)
@@ -101,35 +103,35 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !export { if !createArgs.export {
logger.Generatef("generating Receiver") logger.Generatef("generating Receiver")
} }
receiver := notificationv1.Receiver{ receiver := notificationv1.Receiver{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
Spec: notificationv1.ReceiverSpec{ Spec: notificationv1.ReceiverSpec{
Type: rcvType, Type: receiverArgs.receiverType,
Events: rcvEvents, Events: receiverArgs.events,
Resources: resources, Resources: resources,
SecretRef: corev1.LocalObjectReference{ SecretRef: meta.LocalObjectReference{
Name: rcvSecretRef, Name: receiverArgs.secretRef,
}, },
Suspend: false, Suspend: false,
}, },
} }
if export { if createArgs.export {
return exportReceiver(receiver) return exportReceiver(receiver)
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
@@ -141,7 +143,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
} }
logger.Waitingf("waiting for Receiver reconciliation") logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil { isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err return err
} }

View File

@@ -17,11 +17,15 @@ limitations under the License.
package main package main
import ( import (
"context"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
@@ -35,6 +39,49 @@ func init() {
createCmd.AddCommand(createSecretCmd) createCmd.AddCommand(createSecretCmd)
} }
func makeSecret(name string) (corev1.Secret, error) {
secretLabels, err := parseLabels()
if err != nil {
return corev1.Secret{}, err
}
return corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: rootArgs.namespace,
Labels: secretLabels,
},
StringData: map[string]string{},
Data: nil,
}, nil
}
func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.Secret) error {
namespacedName := types.NamespacedName{
Namespace: secret.GetNamespace(),
Name: secret.GetName(),
}
var existing corev1.Secret
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &secret); err != nil {
return err
} else {
return nil
}
}
return err
}
existing.StringData = secret.StringData
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
}
return nil
}
func exportSecret(secret corev1.Secret) error { func exportSecret(secret corev1.Secret) error {
secret.TypeMeta = metav1.TypeMeta{ secret.TypeMeta = metav1.TypeMeta{
APIVersion: "v1", APIVersion: "v1",

View File

@@ -21,13 +21,13 @@ import (
"crypto/elliptic" "crypto/elliptic"
"fmt" "fmt"
"net/url" "net/url"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/ssh"
) )
var createSecretGitCmd = &cobra.Command{ var createSecretGitCmd = &cobra.Command{
@@ -53,7 +53,7 @@ For Git over HTTP/S, the provided basic authentication credentials are stored in
# Create a Git SSH secret on disk and print the deploy key # Create a Git SSH secret on disk and print the deploy key
flux create secret git podinfo-auth \ flux create secret git podinfo-auth \
--url=ssh://git@github.com/stefanprodan/podinfo \ --url=ssh://git@github.com/stefanprodan/podinfo \
--export > podinfo-auth.yaml --export > podinfo-auth.yaml
yq read podinfo-auth.yaml 'data."identity.pub"' | base64 --decode yq read podinfo-auth.yaml 'data."identity.pub"' | base64 --decode
@@ -61,7 +61,7 @@ For Git over HTTP/S, the provided basic authentication credentials are stored in
flux create secret git podinfo-auth \ flux create secret git podinfo-auth \
--namespace=apps \ --namespace=apps \
--url=ssh://git@github.com/stefanprodan/podinfo \ --url=ssh://git@github.com/stefanprodan/podinfo \
--export > podinfo-auth.yaml --export > podinfo-auth.yaml
sops --encrypt --encrypted-regex '^(data|stringData)$' \ sops --encrypt --encrypted-regex '^(data|stringData)$' \
--in-place podinfo-auth.yaml --in-place podinfo-auth.yaml
@@ -69,60 +69,61 @@ For Git over HTTP/S, the provided basic authentication credentials are stored in
RunE: createSecretGitCmdRun, RunE: createSecretGitCmdRun,
} }
var ( type secretGitFlags struct {
secretGitURL string url string
secretGitUsername string username string
secretGitPassword string password string
secretGitKeyAlgorithm flags.PublicKeyAlgorithm = "rsa" keyAlgorithm flags.PublicKeyAlgorithm
secretGitRSABits flags.RSAKeyBits = 2048 rsaBits flags.RSAKeyBits
secretGitECDSACurve = flags.ECDSACurve{Curve: elliptic.P384()} ecdsaCurve flags.ECDSACurve
) }
var secretGitArgs = NewSecretGitFlags()
func init() { func init() {
createSecretGitCmd.Flags().StringVar(&secretGitURL, "url", "", "git address, e.g. ssh://git@host/org/repository") createSecretGitCmd.Flags().StringVar(&secretGitArgs.url, "url", "", "git address, e.g. ssh://git@host/org/repository")
createSecretGitCmd.Flags().StringVarP(&secretGitUsername, "username", "u", "", "basic authentication username") createSecretGitCmd.Flags().StringVarP(&secretGitArgs.username, "username", "u", "", "basic authentication username")
createSecretGitCmd.Flags().StringVarP(&secretGitPassword, "password", "p", "", "basic authentication password") createSecretGitCmd.Flags().StringVarP(&secretGitArgs.password, "password", "p", "", "basic authentication password")
createSecretGitCmd.Flags().Var(&secretGitKeyAlgorithm, "ssh-key-algorithm", sourceGitKeyAlgorithm.Description()) createSecretGitCmd.Flags().Var(&secretGitArgs.keyAlgorithm, "ssh-key-algorithm", secretGitArgs.keyAlgorithm.Description())
createSecretGitCmd.Flags().Var(&secretGitRSABits, "ssh-rsa-bits", sourceGitRSABits.Description()) createSecretGitCmd.Flags().Var(&secretGitArgs.rsaBits, "ssh-rsa-bits", secretGitArgs.rsaBits.Description())
createSecretGitCmd.Flags().Var(&secretGitECDSACurve, "ssh-ecdsa-curve", sourceGitECDSACurve.Description()) createSecretGitCmd.Flags().Var(&secretGitArgs.ecdsaCurve, "ssh-ecdsa-curve", secretGitArgs.ecdsaCurve.Description())
createSecretCmd.AddCommand(createSecretGitCmd) createSecretCmd.AddCommand(createSecretGitCmd)
} }
func NewSecretGitFlags() secretGitFlags {
return secretGitFlags{
keyAlgorithm: "rsa",
rsaBits: 2048,
ecdsaCurve: flags.ECDSACurve{Curve: elliptic.P384()},
}
}
func createSecretGitCmdRun(cmd *cobra.Command, args []string) error { func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("secret name is required") return fmt.Errorf("secret name is required")
} }
name := args[0] name := args[0]
secret, err := makeSecret(name)
if secretGitURL == "" {
return fmt.Errorf("url is required")
}
u, err := url.Parse(secretGitURL)
if err != nil {
return fmt.Errorf("git URL parse failed: %w", err)
}
secretLabels, err := parseLabels()
if err != nil { if err != nil {
return err return err
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) if secretGitArgs.url == "" {
defer cancel() return fmt.Errorf("url is required")
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: secretLabels,
},
} }
u, err := url.Parse(secretGitArgs.url)
if err != nil {
return fmt.Errorf("git URL parse failed: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
switch u.Scheme { switch u.Scheme {
case "ssh": case "ssh":
pair, err := generateKeyPair(ctx) pair, err := generateKeyPair(ctx, secretGitArgs.keyAlgorithm, secretGitArgs.rsaBits, secretGitArgs.ecdsaCurve)
if err != nil { if err != nil {
return err return err
} }
@@ -132,34 +133,34 @@ 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 !export { if !createArgs.export {
logger.Generatef("deploy key: %s", string(pair.PublicKey)) logger.Generatef("deploy key: %s", string(pair.PublicKey))
} }
case "http", "https": case "http", "https":
if secretGitUsername == "" || secretGitPassword == "" { if secretGitArgs.username == "" || secretGitArgs.password == "" {
return fmt.Errorf("for Git over HTTP/S the username and password are required") return fmt.Errorf("for Git over HTTP/S the username and password are required")
} }
// 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(secretGitUsername), "username": secretGitArgs.username,
"password": []byte(secretGitPassword), "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)
} }
if export { if createArgs.export {
return exportSecret(secret) return exportSecret(secret)
} }
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
@@ -167,7 +168,38 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
if err := upsertSecret(ctx, kubeClient, secret); err != nil { if err := upsertSecret(ctx, kubeClient, secret); err != nil {
return err return err
} }
logger.Actionf("secret '%s' created in '%s' namespace", name, namespace) logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
return nil return nil
} }
func generateKeyPair(ctx context.Context, alg flags.PublicKeyAlgorithm, rsa flags.RSAKeyBits, ecdsa flags.ECDSACurve) (*ssh.KeyPair, error) {
var keyGen ssh.KeyPairGenerator
switch algorithm := alg.String(); algorithm {
case "rsa":
keyGen = ssh.NewRSAGenerator(int(rsa))
case "ecdsa":
keyGen = ssh.NewECDSAGenerator(ecdsa.Curve)
case "ed25519":
keyGen = ssh.NewEd25519Generator()
default:
return nil, fmt.Errorf("unsupported public key algorithm: %s", algorithm)
}
pair, err := keyGen.Generate()
if err != nil {
return nil, fmt.Errorf("key pair generation failed, error: %w", err)
}
return pair, nil
}
func scanHostKey(ctx context.Context, url *url.URL) ([]byte, error) {
host := url.Host
if url.Port() == "" {
host = host + ":22"
}
hostKey, err := ssh.ScanHostKey(host, 30*time.Second)
if err != nil {
return nil, fmt.Errorf("SSH key scan for host %s failed, error: %w", host, err)
}
return hostKey, nil
}

View File

@@ -0,0 +1,107 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/internal/utils"
)
var createSecretHelmCmd = &cobra.Command{
Use: "helm [name]",
Short: "Create or update a Kubernetes secret for Helm repository authentication",
Long: `
The create secret helm command generates a Kubernetes secret with basic authentication credentials.`,
Example: ` # Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS
flux create secret helm repo-auth \
--namespace=my-namespace \
--username=my-username \
--password=my-password \
--export > repo-auth.yaml
sops --encrypt --encrypted-regex '^(data|stringData)$' \
--in-place repo-auth.yaml
# Create an authentication secret using a custom TLS cert
flux create secret helm repo-auth \
--username=username \
--password=password \
--cert-file=./cert.crt \
--key-file=./key.crt \
--ca-file=./ca.crt
`,
RunE: createSecretHelmCmdRun,
}
type secretHelmFlags struct {
username string
password string
secretTLSFlags
}
var secretHelmArgs secretHelmFlags
func init() {
createSecretHelmCmd.Flags().StringVarP(&secretHelmArgs.username, "username", "u", "", "basic authentication username")
createSecretHelmCmd.Flags().StringVarP(&secretHelmArgs.password, "password", "p", "", "basic authentication password")
initSecretTLSFlags(createSecretHelmCmd.Flags(), &secretHelmArgs.secretTLSFlags)
createSecretCmd.AddCommand(createSecretHelmCmd)
}
func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("secret name is required")
}
name := args[0]
secret, err := makeSecret(name)
if err != nil {
return err
}
if secretHelmArgs.username != "" && secretHelmArgs.password != "" {
secret.StringData["username"] = secretHelmArgs.username
secret.StringData["password"] = secretHelmArgs.password
}
if err = populateSecretTLS(&secret, secretHelmArgs.secretTLSFlags); err != nil {
return err
}
if createArgs.export {
return exportSecret(secret)
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
return err
}
logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
return nil
}

View File

@@ -0,0 +1,128 @@
/*
Copyright 2020, 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"io/ioutil"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
corev1 "k8s.io/api/core/v1"
"github.com/fluxcd/flux2/internal/utils"
)
var createSecretTLSCmd = &cobra.Command{
Use: "tls [name]",
Short: "Create or update a Kubernetes secret with TLS certificates",
Long: `
The create secret tls command generates a Kubernetes secret with certificates for use with TLS.`,
Example: `
# Create a TLS secret on disk and encrypt it with Mozilla SOPS.
# Files are expected to be PEM-encoded.
flux create secret tls certs \
--namespace=my-namespace \
--cert-file=./client.crt \
--key-file=./client.key \
--export > certs.yaml
sops --encrypt --encrypted-regex '^(data|stringData)$' \
--in-place certs.yaml
`,
RunE: createSecretTLSCmdRun,
}
type secretTLSFlags struct {
certFile string
keyFile string
caFile string
}
var secretTLSArgs secretTLSFlags
func initSecretTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) {
flags.StringVar(&args.certFile, "cert-file", "", "TLS authentication cert file path")
flags.StringVar(&args.keyFile, "key-file", "", "TLS authentication key file path")
flags.StringVar(&args.caFile, "ca-file", "", "TLS authentication CA file path")
}
func init() {
flags := createSecretTLSCmd.Flags()
initSecretTLSFlags(flags, &secretTLSArgs)
createSecretCmd.AddCommand(createSecretTLSCmd)
}
func populateSecretTLS(secret *corev1.Secret, args secretTLSFlags) error {
if args.certFile != "" && args.keyFile != "" {
cert, err := ioutil.ReadFile(args.certFile)
if err != nil {
return fmt.Errorf("failed to read repository cert file '%s': %w", args.certFile, err)
}
secret.StringData["certFile"] = string(cert)
key, err := ioutil.ReadFile(args.keyFile)
if err != nil {
return fmt.Errorf("failed to read repository key file '%s': %w", args.keyFile, err)
}
secret.StringData["keyFile"] = string(key)
}
if args.caFile != "" {
ca, err := ioutil.ReadFile(args.caFile)
if err != nil {
return fmt.Errorf("failed to read repository CA file '%s': %w", args.caFile, err)
}
secret.StringData["caFile"] = string(ca)
}
return nil
}
func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("secret name is required")
}
name := args[0]
secret, err := makeSecret(name)
if err != nil {
return err
}
if err = populateSecretTLS(&secret, secretTLSArgs); err != nil {
return err
}
if createArgs.export {
return exportSecret(secret)
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
return err
}
logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
return nil
}

View File

@@ -30,9 +30,11 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
) )
var createSourceBucketCmd = &cobra.Command{ var createSourceBucketCmd = &cobra.Command{
@@ -61,41 +63,49 @@ For Buckets with static authentication, the credentials are stored in a Kubernet
RunE: createSourceBucketCmdRun, RunE: createSourceBucketCmdRun,
} }
var ( type sourceBucketFlags struct {
sourceBucketName string name string
sourceBucketProvider = flags.SourceBucketProvider(sourcev1.GenericBucketProvider) provider flags.SourceBucketProvider
sourceBucketEndpoint string endpoint string
sourceBucketAccessKey string accessKey string
sourceBucketSecretKey string secretKey string
sourceBucketRegion string region string
sourceBucketInsecure bool insecure bool
sourceBucketSecretRef string secretRef string
) }
var sourceBucketArgs = NewSourceBucketFlags()
func init() { func init() {
createSourceBucketCmd.Flags().Var(&sourceBucketProvider, "provider", sourceBucketProvider.Description()) createSourceBucketCmd.Flags().Var(&sourceBucketArgs.provider, "provider", sourceBucketArgs.provider.Description())
createSourceBucketCmd.Flags().StringVar(&sourceBucketName, "bucket-name", "", "the bucket name") createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.name, "bucket-name", "", "the bucket name")
createSourceBucketCmd.Flags().StringVar(&sourceBucketEndpoint, "endpoint", "", "the bucket endpoint address") createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.endpoint, "endpoint", "", "the bucket endpoint address")
createSourceBucketCmd.Flags().StringVar(&sourceBucketAccessKey, "access-key", "", "the bucket access key") createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.accessKey, "access-key", "", "the bucket access key")
createSourceBucketCmd.Flags().StringVar(&sourceBucketSecretKey, "secret-key", "", "the bucket secret key") createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.secretKey, "secret-key", "", "the bucket secret key")
createSourceBucketCmd.Flags().StringVar(&sourceBucketRegion, "region", "", "the bucket region") createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.region, "region", "", "the bucket region")
createSourceBucketCmd.Flags().BoolVar(&sourceBucketInsecure, "insecure", false, "for when connecting to a non-TLS S3 HTTP endpoint") createSourceBucketCmd.Flags().BoolVar(&sourceBucketArgs.insecure, "insecure", false, "for when connecting to a non-TLS S3 HTTP endpoint")
createSourceBucketCmd.Flags().StringVar(&sourceBucketSecretRef, "secret-ref", "", "the name of an existing secret containing credentials") createSourceBucketCmd.Flags().StringVar(&sourceBucketArgs.secretRef, "secret-ref", "", "the name of an existing secret containing credentials")
createSourceCmd.AddCommand(createSourceBucketCmd) createSourceCmd.AddCommand(createSourceBucketCmd)
} }
func NewSourceBucketFlags() sourceBucketFlags {
return sourceBucketFlags{
provider: flags.SourceBucketProvider(sourcev1.GenericBucketProvider),
}
}
func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error { func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("Bucket source name is required") return fmt.Errorf("Bucket source name is required")
} }
name := args[0] name := args[0]
if sourceBucketName == "" { if sourceBucketArgs.name == "" {
return fmt.Errorf("bucket-name is required") return fmt.Errorf("bucket-name is required")
} }
if sourceBucketEndpoint == "" { if sourceBucketArgs.endpoint == "" {
return fmt.Errorf("endpoint is required") return fmt.Errorf("endpoint is required")
} }
@@ -113,55 +123,55 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
bucket := &sourcev1.Bucket{ bucket := &sourcev1.Bucket{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
Spec: sourcev1.BucketSpec{ Spec: sourcev1.BucketSpec{
BucketName: sourceBucketName, BucketName: sourceBucketArgs.name,
Provider: sourceBucketProvider.String(), Provider: sourceBucketArgs.provider.String(),
Insecure: sourceBucketInsecure, Insecure: sourceBucketArgs.insecure,
Endpoint: sourceBucketEndpoint, Endpoint: sourceBucketArgs.endpoint,
Region: sourceBucketRegion, Region: sourceBucketArgs.region,
Interval: metav1.Duration{ Interval: metav1.Duration{
Duration: interval, Duration: createArgs.interval,
}, },
}, },
} }
if sourceHelmSecretRef != "" { if sourceHelmArgs.secretRef != "" {
bucket.Spec.SecretRef = &corev1.LocalObjectReference{ bucket.Spec.SecretRef = &meta.LocalObjectReference{
Name: sourceBucketSecretRef, Name: sourceBucketArgs.secretRef,
} }
} }
if export { if createArgs.export {
return exportBucket(*bucket) return exportBucket(*bucket)
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
logger.Generatef("generating Bucket source") logger.Generatef("generating Bucket source")
if sourceBucketSecretRef == "" { if sourceBucketArgs.secretRef == "" {
secretName := fmt.Sprintf("bucket-%s", name) secretName := fmt.Sprintf("bucket-%s", name)
secret := corev1.Secret{ secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: secretName, Name: secretName,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
StringData: map[string]string{}, StringData: map[string]string{},
} }
if sourceBucketAccessKey != "" && sourceBucketSecretKey != "" { if sourceBucketArgs.accessKey != "" && sourceBucketArgs.secretKey != "" {
secret.StringData["accesskey"] = sourceBucketAccessKey secret.StringData["accesskey"] = sourceBucketArgs.accessKey
secret.StringData["secretkey"] = sourceBucketSecretKey secret.StringData["secretkey"] = sourceBucketArgs.secretKey
} }
if len(secret.StringData) > 0 { if len(secret.StringData) > 0 {
@@ -169,7 +179,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
if err := upsertSecret(ctx, kubeClient, secret); err != nil { if err := upsertSecret(ctx, kubeClient, secret); err != nil {
return err return err
} }
bucket.Spec.SecretRef = &corev1.LocalObjectReference{ bucket.Spec.SecretRef = &meta.LocalObjectReference{
Name: secretName, Name: secretName,
} }
logger.Successf("authentication configured") logger.Successf("authentication configured")
@@ -183,7 +193,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
} }
logger.Waitingf("waiting for Bucket source reconciliation") logger.Waitingf("waiting for Bucket source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isBucketReady(ctx, kubeClient, namespacedName, bucket)); err != nil { isBucketReady(ctx, kubeClient, namespacedName, bucket)); err != nil {
return err return err
} }

View File

@@ -23,13 +23,7 @@ import (
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"os" "os"
"time"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/manifoldco/promptui" "github.com/manifoldco/promptui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -40,9 +34,28 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/ssh" "github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
) )
type SourceGitFlags struct {
GitURL string
GitBranch string
GitTag string
GitSemver string
GitUsername string
GitPassword string
GitKeyAlgorithm flags.PublicKeyAlgorithm
GitRSABits flags.RSAKeyBits
GitECDSACurve flags.ECDSACurve
GitSecretRef string
GitImplementation flags.GitImplementation
}
var createSourceGitCmd = &cobra.Command{ var createSourceGitCmd = &cobra.Command{
Use: "git [name]", Use: "git [name]",
Short: "Create or update a GitRepository source", Short: "Create or update a GitRepository source",
@@ -87,44 +100,39 @@ For private Git repositories, the basic authentication credentials are stored in
RunE: createSourceGitCmdRun, RunE: createSourceGitCmdRun,
} }
var ( var sourceArgs = NewSourceGitFlags()
sourceGitURL string
sourceGitBranch string
sourceGitTag string
sourceGitSemver string
sourceGitUsername string
sourceGitPassword string
sourceGitKeyAlgorithm flags.PublicKeyAlgorithm = "rsa"
sourceGitRSABits flags.RSAKeyBits = 2048
sourceGitECDSACurve = flags.ECDSACurve{Curve: elliptic.P384()}
sourceGitSecretRef string
sourceGitImplementation string
)
func init() { func init() {
createSourceGitCmd.Flags().StringVar(&sourceGitURL, "url", "", "git address, e.g. ssh://git@host/org/repository") createSourceGitCmd.Flags().StringVar(&sourceArgs.GitURL, "url", "", "git address, e.g. ssh://git@host/org/repository")
createSourceGitCmd.Flags().StringVar(&sourceGitBranch, "branch", "master", "git branch") createSourceGitCmd.Flags().StringVar(&sourceArgs.GitBranch, "branch", "master", "git branch")
createSourceGitCmd.Flags().StringVar(&sourceGitTag, "tag", "", "git tag") createSourceGitCmd.Flags().StringVar(&sourceArgs.GitTag, "tag", "", "git tag")
createSourceGitCmd.Flags().StringVar(&sourceGitSemver, "tag-semver", "", "git tag semver range") createSourceGitCmd.Flags().StringVar(&sourceArgs.GitSemver, "tag-semver", "", "git tag semver range")
createSourceGitCmd.Flags().StringVarP(&sourceGitUsername, "username", "u", "", "basic authentication username") createSourceGitCmd.Flags().StringVarP(&sourceArgs.GitUsername, "username", "u", "", "basic authentication username")
createSourceGitCmd.Flags().StringVarP(&sourceGitPassword, "password", "p", "", "basic authentication password") createSourceGitCmd.Flags().StringVarP(&sourceArgs.GitPassword, "password", "p", "", "basic authentication password")
createSourceGitCmd.Flags().Var(&sourceGitKeyAlgorithm, "ssh-key-algorithm", sourceGitKeyAlgorithm.Description()) createSourceGitCmd.Flags().Var(&sourceArgs.GitKeyAlgorithm, "ssh-key-algorithm", sourceArgs.GitKeyAlgorithm.Description())
createSourceGitCmd.Flags().Var(&sourceGitRSABits, "ssh-rsa-bits", sourceGitRSABits.Description()) createSourceGitCmd.Flags().Var(&sourceArgs.GitRSABits, "ssh-rsa-bits", sourceArgs.GitRSABits.Description())
createSourceGitCmd.Flags().Var(&sourceGitECDSACurve, "ssh-ecdsa-curve", sourceGitECDSACurve.Description()) createSourceGitCmd.Flags().Var(&sourceArgs.GitECDSACurve, "ssh-ecdsa-curve", sourceArgs.GitECDSACurve.Description())
createSourceGitCmd.Flags().StringVarP(&sourceGitSecretRef, "secret-ref", "", "", "the name of an existing secret containing SSH or basic credentials") createSourceGitCmd.Flags().StringVarP(&sourceArgs.GitSecretRef, "secret-ref", "", "", "the name of an existing secret containing SSH or basic credentials")
createSourceGitCmd.Flags().StringVar(&sourceGitImplementation, "git-implementation", "", "the git implementation to use, can be 'go-git' or 'libgit2'") createSourceGitCmd.Flags().Var(&sourceArgs.GitImplementation, "git-implementation", sourceArgs.GitImplementation.Description())
createSourceCmd.AddCommand(createSourceGitCmd) createSourceCmd.AddCommand(createSourceGitCmd)
} }
func NewSourceGitFlags() SourceGitFlags {
return SourceGitFlags{
GitKeyAlgorithm: "rsa",
GitRSABits: 2048,
GitECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()},
}
}
func createSourceGitCmdRun(cmd *cobra.Command, args []string) error { func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("GitRepository source name is required") return fmt.Errorf("GitRepository source name is required")
} }
name := args[0] name := args[0]
if sourceGitURL == "" { if sourceArgs.GitURL == "" {
return fmt.Errorf("url is required") return fmt.Errorf("url is required")
} }
@@ -134,7 +142,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
u, err := url.Parse(sourceGitURL) u, err := url.Parse(sourceArgs.GitURL)
if err != nil { if err != nil {
return fmt.Errorf("git URL parse failed: %w", err) return fmt.Errorf("git URL parse failed: %w", err)
} }
@@ -144,58 +152,57 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !utils.ContainsItemString([]string{sourcev1.GoGitImplementation, sourcev1.LibGit2Implementation, ""}, sourceGitImplementation) {
return fmt.Errorf("Invalid git implementation %q", sourceGitImplementation)
}
gitRepository := sourcev1.GitRepository{ gitRepository := sourcev1.GitRepository{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
Spec: sourcev1.GitRepositorySpec{ Spec: sourcev1.GitRepositorySpec{
URL: sourceGitURL, URL: sourceArgs.GitURL,
Interval: metav1.Duration{ Interval: metav1.Duration{
Duration: interval, Duration: createArgs.interval,
}, },
Reference: &sourcev1.GitRepositoryRef{}, Reference: &sourcev1.GitRepositoryRef{},
GitImplementation: sourceGitImplementation,
}, },
} }
if sourceGitSemver != "" { if sourceArgs.GitImplementation != "" {
gitRepository.Spec.Reference.SemVer = sourceGitSemver gitRepository.Spec.GitImplementation = sourceArgs.GitImplementation.String()
} else if sourceGitTag != "" {
gitRepository.Spec.Reference.Tag = sourceGitTag
} else {
gitRepository.Spec.Reference.Branch = sourceGitBranch
} }
if export { if sourceArgs.GitSemver != "" {
if sourceGitSecretRef != "" { gitRepository.Spec.Reference.SemVer = sourceArgs.GitSemver
gitRepository.Spec.SecretRef = &corev1.LocalObjectReference{ } else if sourceArgs.GitTag != "" {
Name: sourceGitSecretRef, gitRepository.Spec.Reference.Tag = sourceArgs.GitTag
} else {
gitRepository.Spec.Reference.Branch = sourceArgs.GitBranch
}
if createArgs.export {
if sourceArgs.GitSecretRef != "" {
gitRepository.Spec.SecretRef = &meta.LocalObjectReference{
Name: sourceArgs.GitSecretRef,
} }
} }
return exportGit(gitRepository) return exportGit(gitRepository)
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
withAuth := false withAuth := false
// TODO(hidde): move all auth prep to separate func? // TODO(hidde): move all auth prep to separate func?
if sourceGitSecretRef != "" { if sourceArgs.GitSecretRef != "" {
withAuth = true withAuth = true
} else if u.Scheme == "ssh" { } else if u.Scheme == "ssh" {
logger.Generatef("generating deploy key pair") logger.Generatef("generating deploy key pair")
pair, err := generateKeyPair(ctx) pair, err := generateKeyPair(ctx, sourceArgs.GitKeyAlgorithm, sourceArgs.GitRSABits, sourceArgs.GitECDSACurve)
if err != nil { if err != nil {
return err return err
} }
@@ -220,7 +227,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
secret := corev1.Secret{ secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
StringData: map[string]string{ StringData: map[string]string{
@@ -233,17 +240,17 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
withAuth = true withAuth = true
} else if sourceGitUsername != "" && sourceGitPassword != "" { } else if sourceArgs.GitUsername != "" && sourceArgs.GitPassword != "" {
logger.Actionf("applying secret with basic auth credentials") logger.Actionf("applying secret with basic auth credentials")
secret := corev1.Secret{ secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
StringData: map[string]string{ StringData: map[string]string{
"username": sourceGitUsername, "username": sourceArgs.GitUsername,
"password": sourceGitPassword, "password": sourceArgs.GitPassword,
}, },
} }
if err := upsertSecret(ctx, kubeClient, secret); err != nil { if err := upsertSecret(ctx, kubeClient, secret); err != nil {
@@ -260,10 +267,10 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if withAuth { if withAuth {
secretName := name secretName := name
if sourceGitSecretRef != "" { if sourceArgs.GitSecretRef != "" {
secretName = sourceGitSecretRef secretName = sourceArgs.GitSecretRef
} }
gitRepository.Spec.SecretRef = &corev1.LocalObjectReference{ gitRepository.Spec.SecretRef = &meta.LocalObjectReference{
Name: secretName, Name: secretName,
} }
} }
@@ -275,7 +282,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
logger.Waitingf("waiting for GitRepository source reconciliation") logger.Waitingf("waiting for GitRepository source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isGitRepositoryReady(ctx, kubeClient, namespacedName, &gitRepository)); err != nil { isGitRepositoryReady(ctx, kubeClient, namespacedName, &gitRepository)); err != nil {
return err return err
} }
@@ -288,63 +295,6 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func generateKeyPair(ctx context.Context) (*ssh.KeyPair, error) {
var keyGen ssh.KeyPairGenerator
switch algorithm := sourceGitKeyAlgorithm.String(); algorithm {
case "rsa":
keyGen = ssh.NewRSAGenerator(int(sourceGitRSABits))
case "ecdsa":
keyGen = ssh.NewECDSAGenerator(sourceGitECDSACurve.Curve)
case "ed25519":
keyGen = ssh.NewEd25519Generator()
default:
return nil, fmt.Errorf("unsupported public key algorithm: %s", algorithm)
}
pair, err := keyGen.Generate()
if err != nil {
return nil, fmt.Errorf("key pair generation failed, error: %w", err)
}
return pair, nil
}
func scanHostKey(ctx context.Context, url *url.URL) ([]byte, error) {
host := url.Host
if url.Port() == "" {
host = host + ":22"
}
hostKey, err := ssh.ScanHostKey(host, 30*time.Second)
if err != nil {
return nil, fmt.Errorf("SSH key scan for host %s failed, error: %w", host, err)
}
return hostKey, nil
}
func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.Secret) error {
namespacedName := types.NamespacedName{
Namespace: secret.GetNamespace(),
Name: secret.GetName(),
}
var existing corev1.Secret
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &secret); err != nil {
return err
} else {
return nil
}
}
return err
}
existing.StringData = secret.StringData
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
}
return nil
}
func upsertGitRepository(ctx context.Context, kubeClient client.Client, func upsertGitRepository(ctx context.Context, kubeClient client.Client,
gitRepository *sourcev1.GitRepository) (types.NamespacedName, error) { gitRepository *sourcev1.GitRepository) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{

View File

@@ -33,8 +33,9 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/flux2/internal/utils"
) )
var createSourceHelmCmd = &cobra.Command{ var createSourceHelmCmd = &cobra.Command{
@@ -64,24 +65,26 @@ For private Helm repositories, the basic authentication credentials are stored i
RunE: createSourceHelmCmdRun, RunE: createSourceHelmCmdRun,
} }
var ( type sourceHelmFlags struct {
sourceHelmURL string url string
sourceHelmUsername string username string
sourceHelmPassword string password string
sourceHelmCertFile string certFile string
sourceHelmKeyFile string keyFile string
sourceHelmCAFile string caFile string
sourceHelmSecretRef string secretRef string
) }
var sourceHelmArgs sourceHelmFlags
func init() { func init() {
createSourceHelmCmd.Flags().StringVar(&sourceHelmURL, "url", "", "Helm repository address") createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.url, "url", "", "Helm repository address")
createSourceHelmCmd.Flags().StringVarP(&sourceHelmUsername, "username", "u", "", "basic authentication username") createSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.username, "username", "u", "", "basic authentication username")
createSourceHelmCmd.Flags().StringVarP(&sourceHelmPassword, "password", "p", "", "basic authentication password") createSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.password, "password", "p", "", "basic authentication password")
createSourceHelmCmd.Flags().StringVar(&sourceHelmCertFile, "cert-file", "", "TLS authentication cert file path") createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.certFile, "cert-file", "", "TLS authentication cert file path")
createSourceHelmCmd.Flags().StringVar(&sourceHelmKeyFile, "key-file", "", "TLS authentication key file path") createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.keyFile, "key-file", "", "TLS authentication key file path")
createSourceHelmCmd.Flags().StringVar(&sourceHelmCAFile, "ca-file", "", "TLS authentication CA file path") createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.caFile, "ca-file", "", "TLS authentication CA file path")
createSourceHelmCmd.Flags().StringVarP(&sourceHelmSecretRef, "secret-ref", "", "", "the name of an existing secret containing TLS or basic auth credentials") createSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.secretRef, "secret-ref", "", "", "the name of an existing secret containing TLS or basic auth credentials")
createSourceCmd.AddCommand(createSourceHelmCmd) createSourceCmd.AddCommand(createSourceHelmCmd)
} }
@@ -92,7 +95,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
if sourceHelmURL == "" { if sourceHelmArgs.url == "" {
return fmt.Errorf("url is required") return fmt.Errorf("url is required")
} }
@@ -107,78 +110,78 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
if _, err := url.Parse(sourceHelmURL); err != nil { if _, err := url.Parse(sourceHelmArgs.url); err != nil {
return fmt.Errorf("url parse failed: %w", err) return fmt.Errorf("url parse failed: %w", err)
} }
helmRepository := &sourcev1.HelmRepository{ helmRepository := &sourcev1.HelmRepository{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
Spec: sourcev1.HelmRepositorySpec{ Spec: sourcev1.HelmRepositorySpec{
URL: sourceHelmURL, URL: sourceHelmArgs.url,
Interval: metav1.Duration{ Interval: metav1.Duration{
Duration: interval, Duration: createArgs.interval,
}, },
}, },
} }
if sourceHelmSecretRef != "" { if sourceHelmArgs.secretRef != "" {
helmRepository.Spec.SecretRef = &corev1.LocalObjectReference{ helmRepository.Spec.SecretRef = &meta.LocalObjectReference{
Name: sourceHelmSecretRef, Name: sourceHelmArgs.secretRef,
} }
} }
if export { if createArgs.export {
return exportHelmRepository(*helmRepository) return exportHelmRepository(*helmRepository)
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
logger.Generatef("generating HelmRepository source") logger.Generatef("generating HelmRepository source")
if sourceHelmSecretRef == "" { if sourceHelmArgs.secretRef == "" {
secretName := fmt.Sprintf("helm-%s", name) secretName := fmt.Sprintf("helm-%s", name)
secret := corev1.Secret{ secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: secretName, Name: secretName,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels, Labels: sourceLabels,
}, },
StringData: map[string]string{}, StringData: map[string]string{},
} }
if sourceHelmUsername != "" && sourceHelmPassword != "" { if sourceHelmArgs.username != "" && sourceHelmArgs.password != "" {
secret.StringData["username"] = sourceHelmUsername secret.StringData["username"] = sourceHelmArgs.username
secret.StringData["password"] = sourceHelmPassword secret.StringData["password"] = sourceHelmArgs.password
} }
if sourceHelmCertFile != "" && sourceHelmKeyFile != "" { if sourceHelmArgs.certFile != "" && sourceHelmArgs.keyFile != "" {
cert, err := ioutil.ReadFile(sourceHelmCertFile) cert, err := ioutil.ReadFile(sourceHelmArgs.certFile)
if err != nil { if err != nil {
return fmt.Errorf("failed to read repository cert file '%s': %w", sourceHelmCertFile, err) return fmt.Errorf("failed to read repository cert file '%s': %w", sourceHelmArgs.certFile, err)
} }
secret.StringData["certFile"] = string(cert) secret.StringData["certFile"] = string(cert)
key, err := ioutil.ReadFile(sourceHelmKeyFile) key, err := ioutil.ReadFile(sourceHelmArgs.keyFile)
if err != nil { if err != nil {
return fmt.Errorf("failed to read repository key file '%s': %w", sourceHelmKeyFile, err) return fmt.Errorf("failed to read repository key file '%s': %w", sourceHelmArgs.keyFile, err)
} }
secret.StringData["keyFile"] = string(key) secret.StringData["keyFile"] = string(key)
} }
if sourceHelmCAFile != "" { if sourceHelmArgs.caFile != "" {
ca, err := ioutil.ReadFile(sourceHelmCAFile) ca, err := ioutil.ReadFile(sourceHelmArgs.caFile)
if err != nil { if err != nil {
return fmt.Errorf("failed to read repository CA file '%s': %w", sourceHelmCAFile, err) return fmt.Errorf("failed to read repository CA file '%s': %w", sourceHelmArgs.caFile, err)
} }
secret.StringData["caFile"] = string(ca) secret.StringData["caFile"] = string(ca)
} }
@@ -188,7 +191,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
if err := upsertSecret(ctx, kubeClient, secret); err != nil { if err := upsertSecret(ctx, kubeClient, secret); err != nil {
return err return err
} }
helmRepository.Spec.SecretRef = &corev1.LocalObjectReference{ helmRepository.Spec.SecretRef = &meta.LocalObjectReference{
Name: secretName, Name: secretName,
} }
logger.Successf("authentication configured") logger.Successf("authentication configured")
@@ -202,7 +205,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
} }
logger.Waitingf("waiting for HelmRepository source reconciliation") logger.Waitingf("waiting for HelmRepository source reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isHelmRepositoryReady(ctx, kubeClient, namespacedName, helmRepository)); err != nil { isHelmRepositoryReady(ctx, kubeClient, namespacedName, helmRepository)); err != nil {
return err return err
} }

View File

@@ -58,14 +58,16 @@ const (
tenantLabel = "toolkit.fluxcd.io/tenant" tenantLabel = "toolkit.fluxcd.io/tenant"
) )
var ( type tenantFlags struct {
tenantNamespaces []string namespaces []string
tenantClusterRole string clusterRole string
) }
var tenantArgs tenantFlags
func init() { func init() {
createTenantCmd.Flags().StringSliceVar(&tenantNamespaces, "with-namespace", nil, "namespace belonging to this tenant") createTenantCmd.Flags().StringSliceVar(&tenantArgs.namespaces, "with-namespace", nil, "namespace belonging to this tenant")
createTenantCmd.Flags().StringVar(&tenantClusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding") createTenantCmd.Flags().StringVar(&tenantArgs.clusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding")
createCmd.AddCommand(createTenantCmd) createCmd.AddCommand(createTenantCmd)
} }
@@ -78,11 +80,11 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid tenant name '%s': %v", tenant, err) return fmt.Errorf("invalid tenant name '%s': %v", tenant, err)
} }
if tenantClusterRole == "" { if tenantArgs.clusterRole == "" {
return fmt.Errorf("cluster-role is required") return fmt.Errorf("cluster-role is required")
} }
if tenantNamespaces == nil { if tenantArgs.namespaces == nil {
return fmt.Errorf("with-namespace is required") return fmt.Errorf("with-namespace is required")
} }
@@ -90,7 +92,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
var accounts []corev1.ServiceAccount var accounts []corev1.ServiceAccount
var roleBindings []rbacv1.RoleBinding var roleBindings []rbacv1.RoleBinding
for _, ns := range tenantNamespaces { for _, ns := range tenantArgs.namespaces {
if err := validation.IsQualifiedName(ns); len(err) > 0 { if err := validation.IsQualifiedName(ns); len(err) > 0 {
return fmt.Errorf("invalid namespace '%s': %v", ns, err) return fmt.Errorf("invalid namespace '%s': %v", ns, err)
} }
@@ -141,14 +143,14 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
RoleRef: rbacv1.RoleRef{ RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io", APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole", Kind: "ClusterRole",
Name: tenantClusterRole, Name: tenantArgs.clusterRole,
}, },
} }
roleBindings = append(roleBindings, roleBinding) roleBindings = append(roleBindings, roleBinding)
} }
if export { if createArgs.export {
for i, _ := range tenantNamespaces { for i, _ := range tenantArgs.namespaces {
if err := exportTenant(namespaces[i], accounts[i], roleBindings[i]); err != nil { if err := exportTenant(namespaces[i], accounts[i], roleBindings[i]); err != nil {
return err return err
} }
@@ -156,15 +158,15 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
return nil return nil
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
for i, _ := range tenantNamespaces { for i, _ := range tenantArgs.namespaces {
logger.Actionf("applying namespace %s", namespaces[i].Name) logger.Actionf("applying namespace %s", namespaces[i].Name)
if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil { if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil {
return err return err

View File

@@ -33,12 +33,14 @@ var deleteCmd = &cobra.Command{
Long: "The delete sub-commands delete sources and resources.", Long: "The delete sub-commands delete sources and resources.",
} }
var ( type deleteFlags struct {
deleteSilent bool silent bool
) }
var deleteArgs deleteFlags
func init() { func init() {
deleteCmd.PersistentFlags().BoolVarP(&deleteSilent, "silent", "s", false, deleteCmd.PersistentFlags().BoolVarP(&deleteArgs.silent, "silent", "s", false,
"delete resource without asking for confirmation") "delete resource without asking for confirmation")
rootCmd.AddCommand(deleteCmd) rootCmd.AddCommand(deleteCmd)
@@ -55,25 +57,25 @@ func (del deleteCommand) run(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
err = kubeClient.Get(ctx, namespacedName, del.object.asRuntimeObject()) err = kubeClient.Get(ctx, namespacedName, del.object.asClientObject())
if err != nil { if err != nil {
return err return err
} }
if !deleteSilent { if !deleteArgs.silent {
prompt := promptui.Prompt{ prompt := promptui.Prompt{
Label: "Are you sure you want to delete this " + del.humanKind, Label: "Are you sure you want to delete this " + del.humanKind,
IsConfirm: true, IsConfirm: true,
@@ -83,8 +85,8 @@ func (del deleteCommand) run(cmd *cobra.Command, args []string) error {
} }
} }
logger.Actionf("deleting %s %s in %s namespace", del.humanKind, name, namespace) logger.Actionf("deleting %s %s in %s namespace", del.humanKind, name, rootArgs.namespace)
err = kubeClient.Delete(ctx, del.object.asRuntimeObject()) err = kubeClient.Delete(ctx, del.object.asClientObject())
if err != nil { if err != nil {
return err return err
} }

View File

@@ -48,16 +48,16 @@ func deleteAlertCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
@@ -67,7 +67,7 @@ func deleteAlertCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !deleteSilent { if !deleteArgs.silent {
prompt := promptui.Prompt{ prompt := promptui.Prompt{
Label: "Are you sure you want to delete this Alert", Label: "Are you sure you want to delete this Alert",
IsConfirm: true, IsConfirm: true,
@@ -77,7 +77,7 @@ func deleteAlertCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
logger.Actionf("deleting alert %s in %s namespace", name, namespace) logger.Actionf("deleting alert %s in %s namespace", name, rootArgs.namespace)
err = kubeClient.Delete(ctx, &alert) err = kubeClient.Delete(ctx, &alert)
if err != nil { if err != nil {
return err return err

View File

@@ -48,16 +48,16 @@ func deleteAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
@@ -67,7 +67,7 @@ func deleteAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !deleteSilent { if !deleteArgs.silent {
prompt := promptui.Prompt{ prompt := promptui.Prompt{
Label: "Are you sure you want to delete this Provider", Label: "Are you sure you want to delete this Provider",
IsConfirm: true, IsConfirm: true,
@@ -77,7 +77,7 @@ func deleteAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
logger.Actionf("deleting provider %s in %s namespace", name, namespace) logger.Actionf("deleting provider %s in %s namespace", name, rootArgs.namespace)
err = kubeClient.Delete(ctx, &alertProvider) err = kubeClient.Delete(ctx, &alertProvider)
if err != nil { if err != nil {
return err return err

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var helmRelease helmv2.HelmRelease
err = kubeClient.Get(ctx, namespacedName, &helmRelease)
if err != nil {
return err
}
if !deleteSilent {
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, namespace)
err = kubeClient.Delete(ctx, &helmRelease)
if err != nil {
return err
}
logger.Successf("release deleted")
return nil
}

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var kustomization kustomizev1.Kustomization
err = kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil {
return err
}
if !deleteSilent {
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, namespace)
err = kubeClient.Delete(ctx, &kustomization)
if err != nil {
return err
}
logger.Successf("kustomization deleted")
return nil
}

View File

@@ -48,16 +48,16 @@ func deleteReceiverCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
@@ -67,7 +67,7 @@ func deleteReceiverCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !deleteSilent { if !deleteArgs.silent {
prompt := promptui.Prompt{ prompt := promptui.Prompt{
Label: "Are you sure you want to delete this Receiver", Label: "Are you sure you want to delete this Receiver",
IsConfirm: true, IsConfirm: true,
@@ -77,7 +77,7 @@ func deleteReceiverCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
logger.Actionf("deleting receiver %s in %s namespace", name, namespace) logger.Actionf("deleting receiver %s in %s namespace", name, rootArgs.namespace)
err = kubeClient.Delete(ctx, &receiver) err = kubeClient.Delete(ctx, &receiver)
if err != nil { if err != nil {
return err return err

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var bucket sourcev1.Bucket
err = kubeClient.Get(ctx, namespacedName, &bucket)
if err != nil {
return err
}
if !deleteSilent {
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, namespace)
err = kubeClient.Delete(ctx, &bucket)
if err != nil {
return err
}
logger.Successf("source deleted")
return nil
}

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var git sourcev1.GitRepository
err = kubeClient.Get(ctx, namespacedName, &git)
if err != nil {
return err
}
if !deleteSilent {
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, namespace)
err = kubeClient.Delete(ctx, &git)
if err != nil {
return err
}
logger.Successf("source deleted")
return nil
}

View File

@@ -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() {
@@ -47,16 +50,16 @@ func deleteSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
@@ -66,7 +69,7 @@ func deleteSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !deleteSilent { if !deleteArgs.silent {
prompt := promptui.Prompt{ prompt := promptui.Prompt{
Label: "Are you sure you want to delete this source", Label: "Are you sure you want to delete this source",
IsConfirm: true, IsConfirm: true,
@@ -76,7 +79,7 @@ func deleteSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
logger.Actionf("deleting source %s in %s namespace", name, namespace) logger.Actionf("deleting source %s in %s namespace", name, rootArgs.namespace)
err = kubeClient.Delete(ctx, &helmRepository) err = kubeClient.Delete(ctx, &helmRepository)
if err != nil { if err != nil {
return err return err

View File

@@ -35,12 +35,14 @@ var exportCmd = &cobra.Command{
Long: "The export sub-commands export resources in YAML format.", Long: "The export sub-commands export resources in YAML format.",
} }
var ( type exportFlags struct {
exportAll bool all bool
) }
var exportArgs exportFlags
func init() { func init() {
exportCmd.PersistentFlags().BoolVar(&exportAll, "all", false, "select all resources") exportCmd.PersistentFlags().BoolVar(&exportArgs.all, "all", false, "select all resources")
rootCmd.AddCommand(exportCmd) rootCmd.AddCommand(exportCmd)
} }
@@ -55,8 +57,7 @@ type exportable interface {
// exportableList represents a type that has a list of values, each of // exportableList represents a type that has a list of values, each of
// which is exportable. // which is exportable.
type exportableList interface { type exportableList interface {
adapter listAdapter
len() int
exportItem(i int) interface{} exportItem(i int) interface{}
} }
@@ -66,26 +67,26 @@ type exportCommand struct {
} }
func (export exportCommand) run(cmd *cobra.Command, args []string) error { func (export exportCommand) run(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 { if !exportArgs.all && len(args) < 1 {
return fmt.Errorf("name is required") return fmt.Errorf("name is required")
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if exportAll { if exportArgs.all {
err = kubeClient.List(ctx, export.list.asRuntimeObject(), client.InNamespace(namespace)) err = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(rootArgs.namespace))
if err != nil { if err != nil {
return err return err
} }
if export.list.len() == 0 { if export.list.len() == 0 {
logger.Failuref("no objects found in %s namespace", namespace) logger.Failuref("no objects found in %s namespace", rootArgs.namespace)
return nil return nil
} }
@@ -97,10 +98,10 @@ func (export exportCommand) run(cmd *cobra.Command, args []string) error {
} else { } else {
name := args[0] name := args[0]
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
err = kubeClient.Get(ctx, namespacedName, export.object.asRuntimeObject()) err = kubeClient.Get(ctx, namespacedName, export.object.asClientObject())
if err != nil { if err != nil {
return err return err
} }

View File

@@ -48,27 +48,27 @@ func init() {
} }
func exportAlertCmdRun(cmd *cobra.Command, args []string) error { func exportAlertCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 { if !exportArgs.all && len(args) < 1 {
return fmt.Errorf("name is required") return fmt.Errorf("name is required")
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if exportAll { if exportArgs.all {
var list notificationv1.AlertList var list notificationv1.AlertList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace)) err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
if err != nil { if err != nil {
return err return err
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logger.Failuref("no alerts found in %s namespace", namespace) logger.Failuref("no alerts found in %s namespace", rootArgs.namespace)
return nil return nil
} }
@@ -80,7 +80,7 @@ func exportAlertCmdRun(cmd *cobra.Command, args []string) error {
} else { } else {
name := args[0] name := args[0]
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var alert notificationv1.Alert var alert notificationv1.Alert

View File

@@ -48,27 +48,27 @@ func init() {
} }
func exportAlertProviderCmdRun(cmd *cobra.Command, args []string) error { func exportAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 { if !exportArgs.all && len(args) < 1 {
return fmt.Errorf("name is required") return fmt.Errorf("name is required")
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if exportAll { if exportArgs.all {
var list notificationv1.ProviderList var list notificationv1.ProviderList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace)) err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
if err != nil { if err != nil {
return err return err
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logger.Failuref("no alertproviders found in %s namespace", namespace) logger.Failuref("no alertproviders found in %s namespace", rootArgs.namespace)
return nil return nil
} }
@@ -80,7 +80,7 @@ func exportAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
} else { } else {
name := args[0] name := args[0]
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var alertProvider notificationv1.Provider var alertProvider notificationv1.Provider

View File

@@ -49,27 +49,27 @@ func init() {
} }
func exportHelmReleaseCmdRun(cmd *cobra.Command, args []string) error { func exportHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 { if !exportArgs.all && len(args) < 1 {
return fmt.Errorf("name is required") return fmt.Errorf("name is required")
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if exportAll { if exportArgs.all {
var list helmv2.HelmReleaseList var list helmv2.HelmReleaseList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace)) err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
if err != nil { if err != nil {
return err return err
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logger.Failuref("no helmrelease found in %s namespace", namespace) logger.Failuref("no helmrelease found in %s namespace", rootArgs.namespace)
return nil return nil
} }
@@ -81,7 +81,7 @@ func exportHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
} else { } else {
name := args[0] name := args[0]
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var helmRelease helmv2.HelmRelease var helmRelease helmv2.HelmRelease

View File

@@ -49,27 +49,27 @@ func init() {
} }
func exportKsCmdRun(cmd *cobra.Command, args []string) error { func exportKsCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 { if !exportArgs.all && len(args) < 1 {
return fmt.Errorf("kustomization name is required") return fmt.Errorf("kustomization name is required")
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if exportAll { if exportArgs.all {
var list kustomizev1.KustomizationList var list kustomizev1.KustomizationList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace)) err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
if err != nil { if err != nil {
return err return err
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logger.Failuref("no kustomizations found in %s namespace", namespace) logger.Failuref("no kustomizations found in %s namespace", rootArgs.namespace)
return nil return nil
} }
@@ -81,7 +81,7 @@ func exportKsCmdRun(cmd *cobra.Command, args []string) error {
} else { } else {
name := args[0] name := args[0]
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var kustomization kustomizev1.Kustomization var kustomization kustomizev1.Kustomization

View File

@@ -48,27 +48,27 @@ func init() {
} }
func exportReceiverCmdRun(cmd *cobra.Command, args []string) error { func exportReceiverCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 { if !exportArgs.all && len(args) < 1 {
return fmt.Errorf("name is required") return fmt.Errorf("name is required")
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if exportAll { if exportArgs.all {
var list notificationv1.ReceiverList var list notificationv1.ReceiverList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace)) err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
if err != nil { if err != nil {
return err return err
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logger.Failuref("no receivers found in %s namespace", namespace) logger.Failuref("no receivers found in %s namespace", rootArgs.namespace)
return nil return nil
} }
@@ -80,7 +80,7 @@ func exportReceiverCmdRun(cmd *cobra.Command, args []string) error {
} else { } else {
name := args[0] name := args[0]
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var receiver notificationv1.Receiver var receiver notificationv1.Receiver

View File

@@ -49,27 +49,27 @@ func init() {
} }
func exportSourceBucketCmdRun(cmd *cobra.Command, args []string) error { func exportSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 { if !exportArgs.all && len(args) < 1 {
return fmt.Errorf("name is required") return fmt.Errorf("name is required")
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if exportAll { if exportArgs.all {
var list sourcev1.BucketList var list sourcev1.BucketList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace)) err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
if err != nil { if err != nil {
return err return err
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logger.Failuref("no source found in %s namespace", namespace) logger.Failuref("no source found in %s namespace", rootArgs.namespace)
return nil return nil
} }
@@ -86,7 +86,7 @@ func exportSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
} else { } else {
name := args[0] name := args[0]
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var bucket sourcev1.Bucket var bucket sourcev1.Bucket

View File

@@ -49,27 +49,27 @@ func init() {
} }
func exportSourceGitCmdRun(cmd *cobra.Command, args []string) error { func exportSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 { if !exportArgs.all && len(args) < 1 {
return fmt.Errorf("name is required") return fmt.Errorf("name is required")
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if exportAll { if exportArgs.all {
var list sourcev1.GitRepositoryList var list sourcev1.GitRepositoryList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace)) err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
if err != nil { if err != nil {
return err return err
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logger.Failuref("no source found in %s namespace", namespace) logger.Failuref("no source found in %s namespace", rootArgs.namespace)
return nil return nil
} }
@@ -86,7 +86,7 @@ func exportSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} else { } else {
name := args[0] name := args[0]
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var repository sourcev1.GitRepository var repository sourcev1.GitRepository

View File

@@ -49,27 +49,27 @@ func init() {
} }
func exportSourceHelmCmdRun(cmd *cobra.Command, args []string) error { func exportSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
if !exportAll && len(args) < 1 { if !exportArgs.all && len(args) < 1 {
return fmt.Errorf("name is required") return fmt.Errorf("name is required")
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if exportAll { if exportArgs.all {
var list sourcev1.HelmRepositoryList var list sourcev1.HelmRepositoryList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace)) err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
if err != nil { if err != nil {
return err return err
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logger.Failuref("no source found in %s namespace", namespace) logger.Failuref("no source found in %s namespace", rootArgs.namespace)
return nil return nil
} }
@@ -86,7 +86,7 @@ func exportSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
} else { } else {
name := args[0] name := args[0]
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var repository sourcev1.HelmRepository var repository sourcev1.HelmRepository

View File

@@ -36,17 +36,20 @@ var getCmd = &cobra.Command{
Long: "The get sub-commands print the statuses of sources and resources.", Long: "The get sub-commands print the statuses of sources and resources.",
} }
var allNamespaces bool type GetFlags struct {
allNamespaces bool
}
var getArgs GetFlags
func init() { func init() {
getCmd.PersistentFlags().BoolVarP(&allNamespaces, "all-namespaces", "A", false, getCmd.PersistentFlags().BoolVarP(&getArgs.allNamespaces, "all-namespaces", "A", false,
"list the requested object(s) across all namespaces") "list the requested object(s) across all namespaces")
rootCmd.AddCommand(getCmd) rootCmd.AddCommand(getCmd)
} }
type summarisable interface { type summarisable interface {
adapter listAdapter
len() int
summariseItem(i int, includeNamespace bool) []string summariseItem(i int, includeNamespace bool) []string
headers(includeNamespace bool) []string headers(includeNamespace bool) []string
} }
@@ -75,32 +78,32 @@ type getCommand struct {
} }
func (get getCommand) run(cmd *cobra.Command, args []string) error { func (get getCommand) run(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
var listOpts []client.ListOption var listOpts []client.ListOption
if !allNamespaces { if !getArgs.allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace)) listOpts = append(listOpts, client.InNamespace(rootArgs.namespace))
} }
err = kubeClient.List(ctx, get.list.asRuntimeObject(), listOpts...) err = kubeClient.List(ctx, get.list.asClientList(), listOpts...)
if err != nil { if err != nil {
return err return err
} }
if get.list.len() == 0 { if get.list.len() == 0 {
logger.Failuref("no %s objects found in %s namespace", get.kind, namespace) logger.Failuref("no %s objects found in %s namespace", get.kind, rootArgs.namespace)
return nil return nil
} }
header := get.list.headers(allNamespaces) header := get.list.headers(getArgs.allNamespaces)
var rows [][]string var rows [][]string
for i := 0; i < get.list.len(); i++ { for i := 0; i < get.list.len(); i++ {
row := get.list.summariseItem(i, allNamespaces) row := get.list.summariseItem(i, getArgs.allNamespaces)
rows = append(rows, row) rows = append(rows, row)
} }
utils.PrintTable(os.Stdout, header, rows) utils.PrintTable(os.Stdout, header, rows)

View File

@@ -47,17 +47,17 @@ func init() {
} }
func getAlertCmdRun(cmd *cobra.Command, args []string) error { func getAlertCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
var listOpts []client.ListOption var listOpts []client.ListOption
if !allNamespaces { if !getArgs.allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace)) listOpts = append(listOpts, client.InNamespace(rootArgs.namespace))
} }
var list notificationv1.AlertList var list notificationv1.AlertList
err = kubeClient.List(ctx, &list, listOpts...) err = kubeClient.List(ctx, &list, listOpts...)
@@ -66,12 +66,12 @@ func getAlertCmdRun(cmd *cobra.Command, args []string) error {
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logger.Failuref("no alerts found in %s namespace", namespace) logger.Failuref("no alerts found in %s namespace", rootArgs.namespace)
return nil return nil
} }
header := []string{"Name", "Ready", "Message", "Suspended"} header := []string{"Name", "Ready", "Message", "Suspended"}
if allNamespaces { if getArgs.allNamespaces {
header = append([]string{"Namespace"}, header...) header = append([]string{"Namespace"}, header...)
} }
var rows [][]string var rows [][]string
@@ -92,7 +92,7 @@ func getAlertCmdRun(cmd *cobra.Command, args []string) error {
strings.Title(strconv.FormatBool(alert.Spec.Suspend)), strings.Title(strconv.FormatBool(alert.Spec.Suspend)),
} }
} }
if allNamespaces { if getArgs.allNamespaces {
row = append([]string{alert.Namespace}, row...) row = append([]string{alert.Namespace}, row...)
} }
rows = append(rows, row) rows = append(rows, row)

View File

@@ -45,17 +45,17 @@ func init() {
} }
func getAlertProviderCmdRun(cmd *cobra.Command, args []string) error { func getAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
var listOpts []client.ListOption var listOpts []client.ListOption
if !allNamespaces { if !getArgs.allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace)) listOpts = append(listOpts, client.InNamespace(rootArgs.namespace))
} }
var list notificationv1.ProviderList var list notificationv1.ProviderList
err = kubeClient.List(ctx, &list, listOpts...) err = kubeClient.List(ctx, &list, listOpts...)
@@ -64,12 +64,12 @@ func getAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logger.Failuref("no providers found in %s namespace", namespace) logger.Failuref("no providers found in %s namespace", rootArgs.namespace)
return nil return nil
} }
header := []string{"Name", "Ready", "Message"} header := []string{"Name", "Ready", "Message"}
if allNamespaces { if getArgs.allNamespaces {
header = append([]string{"Namespace"}, header...) header = append([]string{"Namespace"}, header...)
} }
var rows [][]string var rows [][]string
@@ -88,7 +88,7 @@ func getAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
"waiting to be reconciled", "waiting to be reconciled",
} }
} }
if allNamespaces { if getArgs.allNamespaces {
row = append([]string{provider.Namespace}, row...) row = append([]string{provider.Namespace}, row...)
} }
rows = append(rows, row) rows = append(rows, row)

View File

@@ -17,20 +17,11 @@ limitations under the License.
package main package main
import ( import (
"context"
"os"
"strconv" "strconv"
"strings" "strings"
"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"
"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 getHelmReleaseCmd = &cobra.Command{ var getHelmReleaseCmd = &cobra.Command{
@@ -41,66 +32,28 @@ var getHelmReleaseCmd = &cobra.Command{
Example: ` # List all Helm releases and their status Example: ` # List all Helm releases and their status
flux get helmreleases flux get helmreleases
`, `,
RunE: getHelmReleaseCmdRun, RunE: getCommand{
apiType: helmReleaseType,
list: &helmReleaseListAdapter{&helmv2.HelmReleaseList{}},
}.run,
} }
func init() { func init() {
getCmd.AddCommand(getHelmReleaseCmd) getCmd.AddCommand(getHelmReleaseCmd)
} }
func getHelmReleaseCmdRun(cmd *cobra.Command, args []string) error { func (a helmReleaseListAdapter) summariseItem(i int, includeNamespace bool) []string {
ctx, cancel := context.WithTimeout(context.Background(), timeout) item := a.Items[i]
defer cancel() revision := item.Status.LastAppliedRevision
status, msg := statusAndMessage(item.Status.Conditions)
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) return append(nameColumns(&item, includeNamespace),
if err != nil { status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
return err }
}
func (a helmReleaseListAdapter) headers(includeNamespace bool) []string {
var listOpts []client.ListOption headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if !allNamespaces { if includeNamespace {
listOpts = append(listOpts, client.InNamespace(namespace)) headers = append([]string{"Namespace"}, headers...)
} }
var list helmv2.HelmReleaseList return headers
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no releases found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, helmRelease := range list.Items {
row := []string{}
if c := apimeta.FindStatusCondition(helmRelease.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
helmRelease.GetName(),
string(c.Status),
c.Message,
helmRelease.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(helmRelease.Spec.Suspend)),
}
} else {
row = []string{
helmRelease.GetName(),
string(metav1.ConditionFalse),
"waiting to be reconciled",
helmRelease.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(helmRelease.Spec.Suspend)),
}
}
if allNamespaces {
row = append([]string{helmRelease.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
} }

View File

@@ -17,19 +17,11 @@ limitations under the License.
package main package main
import ( import (
"context"
"os"
"strconv" "strconv"
"strings" "strings"
"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"
"sigs.k8s.io/controller-runtime/pkg/client"
) )
var getKsCmd = &cobra.Command{ var getKsCmd = &cobra.Command{
@@ -40,66 +32,28 @@ var getKsCmd = &cobra.Command{
Example: ` # List all kustomizations and their status Example: ` # List all kustomizations and their status
flux get kustomizations flux get kustomizations
`, `,
RunE: getKsCmdRun, RunE: getCommand{
apiType: kustomizationType,
list: &kustomizationListAdapter{&kustomizev1.KustomizationList{}},
}.run,
} }
func init() { func init() {
getCmd.AddCommand(getKsCmd) getCmd.AddCommand(getKsCmd)
} }
func getKsCmdRun(cmd *cobra.Command, args []string) error { func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool) []string {
ctx, cancel := context.WithTimeout(context.Background(), timeout) item := a.Items[i]
defer cancel() revision := item.Status.LastAppliedRevision
status, msg := statusAndMessage(item.Status.Conditions)
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) return append(nameColumns(&item, includeNamespace),
if err != nil { status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
return err }
}
func (a kustomizationListAdapter) headers(includeNamespace bool) []string {
var listOpts []client.ListOption headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if !allNamespaces { if includeNamespace {
listOpts = append(listOpts, client.InNamespace(namespace)) headers = append([]string{"Namespace"}, headers...)
} }
var list kustomizev1.KustomizationList return headers
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no kustomizations found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, kustomization := range list.Items {
row := []string{}
if c := apimeta.FindStatusCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
kustomization.GetName(),
string(c.Status),
c.Message,
kustomization.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(kustomization.Spec.Suspend)),
}
} else {
row = []string{
kustomization.GetName(),
string(metav1.ConditionFalse),
"waiting to be reconciled",
kustomization.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(kustomization.Spec.Suspend)),
}
}
if allNamespaces {
row = append([]string{kustomization.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
} }

View File

@@ -47,17 +47,17 @@ func init() {
} }
func getReceiverCmdRun(cmd *cobra.Command, args []string) error { func getReceiverCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
var listOpts []client.ListOption var listOpts []client.ListOption
if !allNamespaces { if !getArgs.allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace)) listOpts = append(listOpts, client.InNamespace(rootArgs.namespace))
} }
var list notificationv1.ReceiverList var list notificationv1.ReceiverList
err = kubeClient.List(ctx, &list, listOpts...) err = kubeClient.List(ctx, &list, listOpts...)
@@ -66,12 +66,12 @@ func getReceiverCmdRun(cmd *cobra.Command, args []string) error {
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logger.Failuref("no receivers found in %s namespace", namespace) logger.Failuref("no receivers found in %s namespace", rootArgs.namespace)
return nil return nil
} }
header := []string{"Name", "Ready", "Message", "Suspended"} header := []string{"Name", "Ready", "Message", "Suspended"}
if allNamespaces { if getArgs.allNamespaces {
header = append([]string{"Namespace"}, header...) header = append([]string{"Namespace"}, header...)
} }
var rows [][]string var rows [][]string

View File

@@ -17,19 +17,11 @@ limitations under the License.
package main package main
import ( import (
"context" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"os"
"strconv" "strconv"
"strings" "strings"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-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"
"sigs.k8s.io/controller-runtime/pkg/client"
) )
var getSourceBucketCmd = &cobra.Command{ var getSourceBucketCmd = &cobra.Command{
@@ -42,70 +34,31 @@ var getSourceBucketCmd = &cobra.Command{
# List buckets from all namespaces # List buckets from all namespaces
flux get sources helm --all-namespaces flux get sources helm --all-namespaces
`, `,
RunE: getSourceBucketCmdRun, RunE: getCommand{
apiType: bucketType,
list: &bucketListAdapter{&sourcev1.BucketList{}},
}.run,
} }
func init() { func init() {
getSourceCmd.AddCommand(getSourceBucketCmd) getSourceCmd.AddCommand(getSourceBucketCmd)
} }
func getSourceBucketCmdRun(cmd *cobra.Command, args []string) error { func (a *bucketListAdapter) summariseItem(i int, includeNamespace bool) []string {
ctx, cancel := context.WithTimeout(context.Background(), timeout) item := a.Items[i]
defer cancel() var revision string
if item.GetArtifact() != nil {
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) revision = item.GetArtifact().Revision
if err != nil {
return err
} }
status, msg := statusAndMessage(item.Status.Conditions)
var listOpts []client.ListOption return append(nameColumns(&item, includeNamespace),
if !allNamespaces { status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
listOpts = append(listOpts, client.InNamespace(namespace)) }
}
var list sourcev1.BucketList func (a bucketListAdapter) headers(includeNamespace bool) []string {
err = kubeClient.List(ctx, &list, listOpts...) headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if err != nil { if includeNamespace {
return err headers = append([]string{"Namespace"}, headers...)
} }
return headers
if len(list.Items) == 0 {
logger.Failuref("no bucket sources found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, source := range list.Items {
var row []string
var revision string
if source.GetArtifact() != nil {
revision = source.GetArtifact().Revision
}
if c := apimeta.FindStatusCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
source.GetName(),
string(c.Status),
c.Message,
revision,
strings.Title(strconv.FormatBool(source.Spec.Suspend)),
}
} else {
row = []string{
source.GetName(),
string(metav1.ConditionFalse),
"waiting to be reconciled",
revision,
strings.Title(strconv.FormatBool(source.Spec.Suspend)),
}
}
if allNamespaces {
row = append([]string{source.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
} }

View File

@@ -17,19 +17,11 @@ limitations under the License.
package main package main
import ( import (
"context"
"os"
"strconv" "strconv"
"strings" "strings"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-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"
"sigs.k8s.io/controller-runtime/pkg/client"
) )
var getSourceHelmChartCmd = &cobra.Command{ var getSourceHelmChartCmd = &cobra.Command{
@@ -42,70 +34,31 @@ var getSourceHelmChartCmd = &cobra.Command{
# List Helm charts from all namespaces # List Helm charts from all namespaces
flux get sources chart --all-namespaces flux get sources chart --all-namespaces
`, `,
RunE: getSourceHelmChartCmdRun, RunE: getCommand{
apiType: helmChartType,
list: &helmChartListAdapter{&sourcev1.HelmChartList{}},
}.run,
} }
func init() { func init() {
getSourceCmd.AddCommand(getSourceHelmChartCmd) getSourceCmd.AddCommand(getSourceHelmChartCmd)
} }
func getSourceHelmChartCmdRun(cmd *cobra.Command, args []string) error { func (a *helmChartListAdapter) summariseItem(i int, includeNamespace bool) []string {
ctx, cancel := context.WithTimeout(context.Background(), timeout) item := a.Items[i]
defer cancel() var revision string
if item.GetArtifact() != nil {
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) revision = item.GetArtifact().Revision
if err != nil {
return err
} }
status, msg := statusAndMessage(item.Status.Conditions)
var listOpts []client.ListOption return append(nameColumns(&item, includeNamespace),
if !allNamespaces { status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
listOpts = append(listOpts, client.InNamespace(namespace)) }
}
var list sourcev1.HelmChartList func (a helmChartListAdapter) headers(includeNamespace bool) []string {
err = kubeClient.List(ctx, &list, listOpts...) headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if err != nil { if includeNamespace {
return err headers = append([]string{"Namespace"}, headers...)
} }
return headers
if len(list.Items) == 0 {
logger.Failuref("no chart sources found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, source := range list.Items {
var row []string
var revision string
if source.GetArtifact() != nil {
revision = source.GetArtifact().Revision
}
if c := apimeta.FindStatusCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
source.GetName(),
string(c.Status),
c.Message,
revision,
strings.Title(strconv.FormatBool(source.Spec.Suspend)),
}
} else {
row = []string{
source.GetName(),
string(metav1.ConditionFalse),
"waiting to be reconciled",
revision,
strings.Title(strconv.FormatBool(source.Spec.Suspend)),
}
}
if allNamespaces {
row = append([]string{source.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
} }

View File

@@ -17,19 +17,11 @@ limitations under the License.
package main package main
import ( import (
"context"
"os"
"strconv" "strconv"
"strings" "strings"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-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"
"sigs.k8s.io/controller-runtime/pkg/client"
) )
var getSourceGitCmd = &cobra.Command{ var getSourceGitCmd = &cobra.Command{
@@ -42,70 +34,31 @@ var getSourceGitCmd = &cobra.Command{
# List Git repositories from all namespaces # List Git repositories from all namespaces
flux get sources git --all-namespaces flux get sources git --all-namespaces
`, `,
RunE: getSourceGitCmdRun, RunE: getCommand{
apiType: gitRepositoryType,
list: &gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},
}.run,
} }
func init() { func init() {
getSourceCmd.AddCommand(getSourceGitCmd) getSourceCmd.AddCommand(getSourceGitCmd)
} }
func getSourceGitCmdRun(cmd *cobra.Command, args []string) error { func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool) []string {
ctx, cancel := context.WithTimeout(context.Background(), timeout) item := a.Items[i]
defer cancel() var revision string
if item.GetArtifact() != nil {
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) revision = item.GetArtifact().Revision
if err != nil {
return err
} }
status, msg := statusAndMessage(item.Status.Conditions)
var listOpts []client.ListOption return append(nameColumns(&item, includeNamespace),
if !allNamespaces { status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
listOpts = append(listOpts, client.InNamespace(namespace)) }
}
var list sourcev1.GitRepositoryList func (a gitRepositoryListAdapter) headers(includeNamespace bool) []string {
err = kubeClient.List(ctx, &list, listOpts...) headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if err != nil { if includeNamespace {
return err headers = append([]string{"Namespace"}, headers...)
} }
return headers
if len(list.Items) == 0 {
logger.Failuref("no git sources found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, source := range list.Items {
var row []string
var revision string
if source.GetArtifact() != nil {
revision = source.GetArtifact().Revision
}
if c := apimeta.FindStatusCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
source.GetName(),
string(c.Status),
c.Message,
revision,
strings.Title(strconv.FormatBool(source.Spec.Suspend)),
}
} else {
row = []string{
source.GetName(),
string(metav1.ConditionFalse),
"waiting to be reconciled",
revision,
strings.Title(strconv.FormatBool(source.Spec.Suspend)),
}
}
if allNamespaces {
row = append([]string{source.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
} }

View File

@@ -17,19 +17,11 @@ limitations under the License.
package main package main
import ( import (
"context"
"os"
"strconv" "strconv"
"strings" "strings"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-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"
"sigs.k8s.io/controller-runtime/pkg/client"
) )
var getSourceHelmCmd = &cobra.Command{ var getSourceHelmCmd = &cobra.Command{
@@ -42,70 +34,31 @@ var getSourceHelmCmd = &cobra.Command{
# List Helm repositories from all namespaces # List Helm repositories from all namespaces
flux get sources helm --all-namespaces flux get sources helm --all-namespaces
`, `,
RunE: getSourceHelmCmdRun, RunE: getCommand{
apiType: helmRepositoryType,
list: &helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},
}.run,
} }
func init() { func init() {
getSourceCmd.AddCommand(getSourceHelmCmd) getSourceCmd.AddCommand(getSourceHelmCmd)
} }
func getSourceHelmCmdRun(cmd *cobra.Command, args []string) error { func (a *helmRepositoryListAdapter) summariseItem(i int, includeNamespace bool) []string {
ctx, cancel := context.WithTimeout(context.Background(), timeout) item := a.Items[i]
defer cancel() var revision string
if item.GetArtifact() != nil {
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) revision = item.GetArtifact().Revision
if err != nil {
return err
} }
status, msg := statusAndMessage(item.Status.Conditions)
var listOpts []client.ListOption return append(nameColumns(&item, includeNamespace),
if !allNamespaces { status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
listOpts = append(listOpts, client.InNamespace(namespace)) }
}
var list sourcev1.HelmRepositoryList func (a helmRepositoryListAdapter) headers(includeNamespace bool) []string {
err = kubeClient.List(ctx, &list, listOpts...) headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if err != nil { if includeNamespace {
return err headers = append([]string{"Namespace"}, headers...)
} }
return headers
if len(list.Items) == 0 {
logger.Failuref("no helm sources found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, source := range list.Items {
var row []string
var revision string
if source.GetArtifact() != nil {
revision = source.GetArtifact().Revision
}
if c := apimeta.FindStatusCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
source.GetName(),
string(c.Status),
c.Message,
revision,
strings.Title(strconv.FormatBool(source.Spec.Suspend)),
}
} else {
row = []string{
source.GetName(),
string(metav1.ConditionFalse),
"waiting to be reconciled",
revision,
strings.Title(strconv.FormatBool(source.Spec.Suspend)),
}
}
if allNamespaces {
row = append([]string{source.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
} }

51
cmd/flux/helmrelease.go Normal file
View File

@@ -0,0 +1,51 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// helmv2.HelmRelease
var helmReleaseType = apiType{
kind: helmv2.HelmReleaseKind,
humanKind: "helmreleases",
}
type helmReleaseAdapter struct {
*helmv2.HelmRelease
}
func (h helmReleaseAdapter) asClientObject() client.Object {
return h.HelmRelease
}
// helmv2.HelmReleaseList
type helmReleaseListAdapter struct {
*helmv2.HelmReleaseList
}
func (h helmReleaseListAdapter) asClientList() client.ObjectList {
return h.HelmReleaseList
}
func (h helmReleaseListAdapter) len() int {
return len(h.HelmReleaseList.Items)
}

View File

@@ -17,7 +17,7 @@ limitations under the License.
package main package main
import ( import (
"k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client"
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1" autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
@@ -38,7 +38,7 @@ type imageRepositoryAdapter struct {
*imagev1.ImageRepository *imagev1.ImageRepository
} }
func (a imageRepositoryAdapter) asRuntimeObject() runtime.Object { func (a imageRepositoryAdapter) asClientObject() client.Object {
return a.ImageRepository return a.ImageRepository
} }
@@ -48,7 +48,7 @@ type imageRepositoryListAdapter struct {
*imagev1.ImageRepositoryList *imagev1.ImageRepositoryList
} }
func (a imageRepositoryListAdapter) asRuntimeObject() runtime.Object { func (a imageRepositoryListAdapter) asClientList() client.ObjectList {
return a.ImageRepositoryList return a.ImageRepositoryList
} }
@@ -67,7 +67,7 @@ type imagePolicyAdapter struct {
*imagev1.ImagePolicy *imagev1.ImagePolicy
} }
func (a imagePolicyAdapter) asRuntimeObject() runtime.Object { func (a imagePolicyAdapter) asClientObject() client.Object {
return a.ImagePolicy return a.ImagePolicy
} }
@@ -77,7 +77,7 @@ type imagePolicyListAdapter struct {
*imagev1.ImagePolicyList *imagev1.ImagePolicyList
} }
func (a imagePolicyListAdapter) asRuntimeObject() runtime.Object { func (a imagePolicyListAdapter) asClientList() client.ObjectList {
return a.ImagePolicyList return a.ImagePolicyList
} }
@@ -96,7 +96,7 @@ type imageUpdateAutomationAdapter struct {
*autov1.ImageUpdateAutomation *autov1.ImageUpdateAutomation
} }
func (a imageUpdateAutomationAdapter) asRuntimeObject() runtime.Object { func (a imageUpdateAutomationAdapter) asClientObject() client.Object {
return a.ImageUpdateAutomation return a.ImageUpdateAutomation
} }
@@ -106,7 +106,7 @@ type imageUpdateAutomationListAdapter struct {
*autov1.ImageUpdateAutomationList *autov1.ImageUpdateAutomationList
} }
func (a imageUpdateAutomationListAdapter) asRuntimeObject() runtime.Object { func (a imageUpdateAutomationListAdapter) asClientList() client.ObjectList {
return a.ImageUpdateAutomationList return a.ImageUpdateAutomationList
} }

View File

@@ -62,8 +62,8 @@ var (
installImagePullSecret string installImagePullSecret string
installWatchAllNamespaces bool installWatchAllNamespaces bool
installNetworkPolicy bool installNetworkPolicy bool
installArch = flags.Arch(defaults.Arch) installArch flags.Arch
installLogLevel = flags.LogLevel(defaults.LogLevel) installLogLevel = flags.LogLevel(rootArgs.defaults.LogLevel)
installClusterDomain string installClusterDomain string
) )
@@ -72,33 +72,34 @@ func init() {
"write the install manifests to stdout and exit") "write the install manifests to stdout and exit")
installCmd.Flags().BoolVarP(&installDryRun, "dry-run", "", false, installCmd.Flags().BoolVarP(&installDryRun, "dry-run", "", false,
"only print the object that would be applied") "only print the object that would be applied")
installCmd.Flags().StringVarP(&installVersion, "version", "v", defaults.Version, installCmd.Flags().StringVarP(&installVersion, "version", "v", rootArgs.defaults.Version,
"toolkit version") "toolkit version")
installCmd.Flags().StringSliceVar(&installDefaultComponents, "components", defaults.Components, installCmd.Flags().StringSliceVar(&installDefaultComponents, "components", rootArgs.defaults.Components,
"list of components, accepts comma-separated values") "list of components, accepts comma-separated values")
installCmd.Flags().StringSliceVar(&installExtraComponents, "components-extra", nil, installCmd.Flags().StringSliceVar(&installExtraComponents, "components-extra", nil,
"list of components in addition to those supplied or defaulted, accepts comma-separated values") "list of components in addition to those supplied or defaulted, accepts comma-separated values")
installCmd.Flags().StringVar(&installManifestsPath, "manifests", "", "path to the manifest directory") installCmd.Flags().StringVar(&installManifestsPath, "manifests", "", "path to the manifest directory")
installCmd.Flags().MarkHidden("manifests") installCmd.Flags().StringVar(&installRegistry, "registry", rootArgs.defaults.Registry,
installCmd.Flags().StringVar(&installRegistry, "registry", defaults.Registry,
"container registry where the toolkit images are published") "container registry where the toolkit images are published")
installCmd.Flags().StringVar(&installImagePullSecret, "image-pull-secret", "", installCmd.Flags().StringVar(&installImagePullSecret, "image-pull-secret", "",
"Kubernetes secret name used for pulling the toolkit images from a private registry") "Kubernetes secret name used for pulling the toolkit images from a private registry")
installCmd.Flags().Var(&installArch, "arch", installArch.Description()) installCmd.Flags().Var(&installArch, "arch", installArch.Description())
installCmd.Flags().BoolVar(&installWatchAllNamespaces, "watch-all-namespaces", defaults.WatchAllNamespaces, installCmd.Flags().BoolVar(&installWatchAllNamespaces, "watch-all-namespaces", rootArgs.defaults.WatchAllNamespaces,
"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed") "watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed")
installCmd.Flags().Var(&installLogLevel, "log-level", installLogLevel.Description()) installCmd.Flags().Var(&installLogLevel, "log-level", installLogLevel.Description())
installCmd.Flags().BoolVar(&installNetworkPolicy, "network-policy", defaults.NetworkPolicy, installCmd.Flags().BoolVar(&installNetworkPolicy, "network-policy", rootArgs.defaults.NetworkPolicy,
"deny ingress access to the toolkit controllers from other namespaces using network policies") "deny ingress access to the toolkit controllers from other namespaces using network policies")
installCmd.Flags().StringVar(&installClusterDomain, "cluster-domain", defaults.ClusterDomain, "internal cluster domain") installCmd.Flags().StringVar(&installClusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
installCmd.Flags().MarkHidden("manifests")
installCmd.Flags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64")
rootCmd.AddCommand(installCmd) rootCmd.AddCommand(installCmd)
} }
func installCmdRun(cmd *cobra.Command, args []string) error { func installCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
tmpDir, err := ioutil.TempDir("", namespace) tmpDir, err := ioutil.TempDir("", rootArgs.namespace)
if err != nil { if err != nil {
return err return err
} }
@@ -110,20 +111,23 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
components := append(installDefaultComponents, installExtraComponents...) components := append(installDefaultComponents, installExtraComponents...)
if err := utils.ValidateComponents(components); err != nil {
return err
}
opts := install.Options{ opts := install.Options{
BaseURL: installManifestsPath, BaseURL: installManifestsPath,
Version: installVersion, Version: installVersion,
Namespace: namespace, Namespace: rootArgs.namespace,
Components: components, Components: components,
Registry: installRegistry, Registry: installRegistry,
ImagePullSecret: installImagePullSecret, ImagePullSecret: installImagePullSecret,
Arch: installArch.String(),
WatchAllNamespaces: installWatchAllNamespaces, WatchAllNamespaces: installWatchAllNamespaces,
NetworkPolicy: installNetworkPolicy, NetworkPolicy: installNetworkPolicy,
LogLevel: installLogLevel.String(), LogLevel: installLogLevel.String(),
NotificationController: defaults.NotificationController, NotificationController: rootArgs.defaults.NotificationController,
ManifestFile: fmt.Sprintf("%s.yaml", namespace), ManifestFile: fmt.Sprintf("%s.yaml", rootArgs.namespace),
Timeout: timeout, Timeout: rootArgs.timeout,
ClusterDomain: installClusterDomain, ClusterDomain: installClusterDomain,
} }
@@ -140,7 +144,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("install failed: %w", err) return fmt.Errorf("install failed: %w", err)
} }
if verbose { if rootArgs.verbose {
fmt.Print(manifest.Content) fmt.Print(manifest.Content)
} else if installExport { } else if installExport {
fmt.Println("---") fmt.Println("---")
@@ -152,9 +156,9 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
} }
logger.Successf("manifests build completed") logger.Successf("manifests build completed")
logger.Actionf("installing components in %s namespace", namespace) logger.Actionf("installing components in %s namespace", rootArgs.namespace)
applyOutput := utils.ModeStderrOS applyOutput := utils.ModeStderrOS
if verbose { if rootArgs.verbose {
applyOutput = utils.ModeOS applyOutput = utils.ModeOS
} }
@@ -163,7 +167,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
kubectlArgs = append(kubectlArgs, "--dry-run=client") kubectlArgs = append(kubectlArgs, "--dry-run=client")
applyOutput = utils.ModeOS applyOutput = utils.ModeOS
} }
if _, err := utils.ExecKubectlCommand(ctx, applyOutput, kubeconfig, kubecontext, kubectlArgs...); err != nil { if _, err := utils.ExecKubectlCommand(ctx, applyOutput, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
return fmt.Errorf("install failed") return fmt.Errorf("install failed")
} }
@@ -176,8 +180,8 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Waitingf("verifying installation") logger.Waitingf("verifying installation")
for _, deployment := range components { for _, deployment := range components {
kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()} kubectlArgs = []string{"-n", rootArgs.namespace, "rollout", "status", "deployment", deployment, "--timeout", rootArgs.timeout.String()}
if _, err := utils.ExecKubectlCommand(ctx, applyOutput, kubeconfig, kubecontext, kubectlArgs...); err != nil { if _, err := utils.ExecKubectlCommand(ctx, applyOutput, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
return fmt.Errorf("install failed") return fmt.Errorf("install failed")
} else { } else {
logger.Successf("%s ready", deployment) logger.Successf("%s ready", deployment)

51
cmd/flux/kustomization.go Normal file
View File

@@ -0,0 +1,51 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// kustomizev1.Kustomization
var kustomizationType = apiType{
kind: kustomizev1.KustomizationKind,
humanKind: "kustomizations",
}
type kustomizationAdapter struct {
*kustomizev1.Kustomization
}
func (a kustomizationAdapter) asClientObject() client.Object {
return a.Kustomization
}
// kustomizev1.KustomizationList
type kustomizationListAdapter struct {
*kustomizev1.KustomizationList
}
func (a kustomizationListAdapter) asClientList() client.ObjectList {
return a.KustomizationList
}
func (a kustomizationListAdapter) len() int {
return len(a.KustomizationList.Items)
}

View File

@@ -26,7 +26,6 @@ import (
"github.com/spf13/cobra/doc" "github.com/spf13/cobra/doc"
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
fluxlog "github.com/fluxcd/flux2/pkg/log"
"github.com/fluxcd/flux2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen/install"
) )
@@ -94,22 +93,32 @@ var rootCmd = &cobra.Command{
`, `,
} }
var ( var logger = stderrLogger{stderr: os.Stderr}
type rootFlags struct {
kubeconfig string kubeconfig string
kubecontext string kubecontext string
namespace string namespace string
timeout time.Duration timeout time.Duration
verbose bool verbose bool
pollInterval = 2 * time.Second pollInterval time.Duration
logger fluxlog.Logger = stderrLogger{stderr: os.Stderr} defaults install.Options
defaults = install.MakeDefaultOptions() }
)
var rootArgs = NewRootFlags()
func init() { func init() {
rootCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", defaults.Namespace, "the namespace scope for this operation") rootCmd.PersistentFlags().StringVarP(&rootArgs.namespace, "namespace", "n", rootArgs.defaults.Namespace, "the namespace scope for this operation")
rootCmd.PersistentFlags().DurationVar(&timeout, "timeout", 5*time.Minute, "timeout for this operation") rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 5*time.Minute, "timeout for this operation")
rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "print generated objects") rootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, "verbose", false, "print generated objects")
rootCmd.PersistentFlags().StringVarP(&kubecontext, "context", "", "", "kubernetes context to use") rootCmd.PersistentFlags().StringVarP(&rootArgs.kubecontext, "context", "", "", "kubernetes context to use")
}
func NewRootFlags() rootFlags {
return rootFlags{
pollInterval: 2 * time.Second,
defaults: install.MakeDefaultOptions(),
}
} }
func main() { func main() {
@@ -124,22 +133,22 @@ func main() {
func kubeconfigFlag() { func kubeconfigFlag() {
if home := homeDir(); home != "" { if home := homeDir(); home != "" {
rootCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "", filepath.Join(home, ".kube", "config"), rootCmd.PersistentFlags().StringVarP(&rootArgs.kubeconfig, "kubeconfig", "", filepath.Join(home, ".kube", "config"),
"path to the kubeconfig file") "path to the kubeconfig file")
} else { } else {
rootCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "", "", rootCmd.PersistentFlags().StringVarP(&rootArgs.kubeconfig, "kubeconfig", "", "",
"absolute path to the kubeconfig file") "absolute path to the kubeconfig file")
} }
if len(os.Getenv("KUBECONFIG")) > 0 { if len(os.Getenv("KUBECONFIG")) > 0 {
kubeconfig = os.Getenv("KUBECONFIG") rootArgs.kubeconfig = os.Getenv("KUBECONFIG")
} }
} }
func generateDocs() { func generateDocs() {
args := os.Args[1:] args := os.Args[1:]
if len(args) > 0 && args[0] == "docgen" { if len(args) > 0 && args[0] == "docgen" {
rootCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "", "~/.kube/config", rootCmd.PersistentFlags().StringVarP(&rootArgs.kubeconfig, "kubeconfig", "", "~/.kube/config",
"path to the kubeconfig file") "path to the kubeconfig file")
rootCmd.DisableAutoGenTag = true rootCmd.DisableAutoGenTag = true
err := doc.GenMarkdownTree(rootCmd, "./docs/cmd") err := doc.GenMarkdownTree(rootCmd, "./docs/cmd")

View File

@@ -17,7 +17,7 @@ limitations under the License.
package main package main
import ( import (
"k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client"
) )
// Most commands need one or both of the kind (e.g., // Most commands need one or both of the kind (e.g.,
@@ -35,16 +35,24 @@ type apiType struct {
// use values of the wrapper with `client.Client`, which only deals // use values of the wrapper with `client.Client`, which only deals
// with types that have been added to the schema. // with types that have been added to the schema.
type adapter interface { type adapter interface {
asRuntimeObject() runtime.Object asClientObject() client.Object
} }
// universalAdapter is an adapter for any runtime.Object. Use this if // listAdapater is the analogue to adapter, but for lists; the
// controller runtime distinguishes between methods dealing with
// objects and lists.
type listAdapter interface {
asClientList() client.ObjectList
len() int
}
// universalAdapter is an adapter for any client.Object. Use this if
// there are no other methods needed. // there are no other methods needed.
type universalAdapter struct { type universalAdapter struct {
obj runtime.Object obj client.Object
} }
func (c universalAdapter) asRuntimeObject() runtime.Object { func (c universalAdapter) asClientObject() client.Object {
return c.obj return c.obj
} }

View File

@@ -69,20 +69,20 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
err = kubeClient.Get(ctx, namespacedName, reconcile.object.asRuntimeObject()) err = kubeClient.Get(ctx, namespacedName, reconcile.object.asClientObject())
if err != nil { if err != nil {
return err return err
} }
@@ -91,7 +91,7 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
return fmt.Errorf("resource is suspended") return fmt.Errorf("resource is suspended")
} }
logger.Actionf("annotating %s %s in %s namespace", reconcile.kind, name, namespace) logger.Actionf("annotating %s %s in %s namespace", reconcile.kind, name, rootArgs.namespace)
if err := requestReconciliation(ctx, kubeClient, namespacedName, reconcile.object); err != nil { if err := requestReconciliation(ctx, kubeClient, namespacedName, reconcile.object); err != nil {
return err return err
} }
@@ -99,7 +99,7 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest() lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()
logger.Waitingf("waiting for %s reconciliation", reconcile.kind) logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil { reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
return err return err
} }
@@ -115,7 +115,7 @@ func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
func reconciliationHandled(ctx context.Context, kubeClient client.Client, func reconciliationHandled(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionFunc { namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionFunc {
return func() (bool, error) { return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, obj.asRuntimeObject()) err := kubeClient.Get(ctx, namespacedName, obj.asClientObject())
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -126,17 +126,17 @@ func reconciliationHandled(ctx context.Context, kubeClient client.Client,
func requestReconciliation(ctx context.Context, kubeClient client.Client, func requestReconciliation(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, obj reconcilable) error { namespacedName types.NamespacedName, obj reconcilable) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
if err := kubeClient.Get(ctx, namespacedName, obj.asRuntimeObject()); err != nil { if err := kubeClient.Get(ctx, namespacedName, obj.asClientObject()); err != nil {
return err return err
} }
if ann := obj.GetAnnotations(); ann == nil { if ann := obj.GetAnnotations(); ann == nil {
obj.SetAnnotations(map[string]string{ obj.SetAnnotations(map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
}) })
} else { } else {
ann[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) ann[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
obj.SetAnnotations(ann) obj.SetAnnotations(ann)
} }
return kubeClient.Update(ctx, obj.asRuntimeObject()) return kubeClient.Update(ctx, obj.asClientObject())
}) })
} }

View File

@@ -51,16 +51,16 @@ func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
@@ -74,13 +74,13 @@ func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("resource is suspended") return fmt.Errorf("resource is suspended")
} }
logger.Actionf("annotating Alert %s in %s namespace", name, namespace) logger.Actionf("annotating Alert %s in %s namespace", name, rootArgs.namespace)
if alert.Annotations == nil { if alert.Annotations == nil {
alert.Annotations = map[string]string{ alert.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
} }
} else { } else {
alert.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) alert.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
} }
if err := kubeClient.Update(ctx, &alert); err != nil { if err := kubeClient.Update(ctx, &alert); err != nil {
@@ -89,7 +89,7 @@ func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("Alert annotated") logger.Successf("Alert annotated")
logger.Waitingf("waiting for reconciliation") logger.Waitingf("waiting for reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil { isAlertReady(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err return err
} }

View File

@@ -51,20 +51,20 @@ func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
logger.Actionf("annotating Provider %s in %s namespace", name, namespace) logger.Actionf("annotating Provider %s in %s namespace", name, rootArgs.namespace)
var alertProvider notificationv1.Provider var alertProvider notificationv1.Provider
err = kubeClient.Get(ctx, namespacedName, &alertProvider) err = kubeClient.Get(ctx, namespacedName, &alertProvider)
if err != nil { if err != nil {
@@ -73,10 +73,10 @@ func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
if alertProvider.Annotations == nil { if alertProvider.Annotations == nil {
alertProvider.Annotations = map[string]string{ alertProvider.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
} }
} else { } else {
alertProvider.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) alertProvider.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
} }
if err := kubeClient.Update(ctx, &alertProvider); err != nil { if err := kubeClient.Update(ctx, &alertProvider); err != nil {
return err return err
@@ -84,7 +84,7 @@ func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("Provider annotated") logger.Successf("Provider annotated")
logger.Waitingf("waiting for reconciliation") logger.Waitingf("waiting for reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isAlertProviderReady(ctx, kubeClient, namespacedName, &alertProvider)); err != nil { isAlertProviderReady(ctx, kubeClient, namespacedName, &alertProvider)); err != nil {
return err return err
} }

View File

@@ -51,12 +51,14 @@ The reconcile kustomization command triggers a reconciliation of a HelmRelease r
RunE: reconcileHrCmdRun, RunE: reconcileHrCmdRun,
} }
var ( type reconcileHelmReleaseFlags struct {
syncHrWithSource bool syncHrWithSource bool
) }
var rhrArgs reconcileHelmReleaseFlags
func init() { func init() {
reconcileHrCmd.Flags().BoolVar(&syncHrWithSource, "with-source", false, "reconcile HelmRelease source") reconcileHrCmd.Flags().BoolVar(&rhrArgs.syncHrWithSource, "with-source", false, "reconcile HelmRelease source")
reconcileCmd.AddCommand(reconcileHrCmd) reconcileCmd.AddCommand(reconcileHrCmd)
} }
@@ -67,16 +69,16 @@ func reconcileHrCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
@@ -90,29 +92,43 @@ func reconcileHrCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("resource is suspended") return fmt.Errorf("resource is suspended")
} }
if 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
logger.Actionf("annotating HelmRelease %s in %s namespace", name, namespace) logger.Actionf("annotating HelmRelease %s in %s namespace", name, rootArgs.namespace)
if err := requestHelmReleaseReconciliation(ctx, kubeClient, namespacedName, &helmRelease); err != nil { if err := requestHelmReleaseReconciliation(ctx, kubeClient, namespacedName, &helmRelease); err != nil {
return err return err
} }
logger.Successf("HelmRelease annotated") logger.Successf("HelmRelease annotated")
logger.Waitingf("waiting for HelmRelease reconciliation") logger.Waitingf("waiting for HelmRelease reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
helmReleaseReconciliationHandled(ctx, kubeClient, namespacedName, &helmRelease, lastHandledReconcileAt), helmReleaseReconciliationHandled(ctx, kubeClient, namespacedName, &helmRelease, lastHandledReconcileAt),
); err != nil { ); err != nil {
return err return err
@@ -153,10 +169,10 @@ func requestHelmReleaseReconciliation(ctx context.Context, kubeClient client.Cli
} }
if helmRelease.Annotations == nil { if helmRelease.Annotations == nil {
helmRelease.Annotations = map[string]string{ helmRelease.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
} }
} else { } else {
helmRelease.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) helmRelease.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
} }
return kubeClient.Update(ctx, helmRelease) return kubeClient.Update(ctx, helmRelease)
}) })

View File

@@ -50,12 +50,14 @@ The reconcile kustomization command triggers a reconciliation of a Kustomization
RunE: reconcileKsCmdRun, RunE: reconcileKsCmdRun,
} }
var ( type reconcileKsFlags struct {
syncKsWithSource bool syncKsWithSource bool
) }
var rksArgs reconcileKsFlags
func init() { func init() {
reconcileKsCmd.Flags().BoolVar(&syncKsWithSource, "with-source", false, "reconcile Kustomization source") reconcileKsCmd.Flags().BoolVar(&rksArgs.syncKsWithSource, "with-source", false, "reconcile Kustomization source")
reconcileCmd.AddCommand(reconcileKsCmd) reconcileCmd.AddCommand(reconcileKsCmd)
} }
@@ -66,16 +68,16 @@ func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var kustomization kustomizev1.Kustomization var kustomization kustomizev1.Kustomization
@@ -88,20 +90,31 @@ func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("resource is suspended") return fmt.Errorf("resource is suspended")
} }
if 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
logger.Actionf("annotating Kustomization %s in %s namespace", name, namespace) logger.Actionf("annotating Kustomization %s in %s namespace", name, rootArgs.namespace)
if err := requestKustomizeReconciliation(ctx, kubeClient, namespacedName, &kustomization); err != nil { if err := requestKustomizeReconciliation(ctx, kubeClient, namespacedName, &kustomization); err != nil {
return err return err
} }
@@ -109,7 +122,7 @@ func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
logger.Waitingf("waiting for Kustomization reconciliation") logger.Waitingf("waiting for Kustomization reconciliation")
if err := wait.PollImmediate( if err := wait.PollImmediate(
pollInterval, timeout, rootArgs.pollInterval, rootArgs.timeout,
kustomizeReconciliationHandled(ctx, kubeClient, namespacedName, &kustomization, lastHandledReconcileAt), kustomizeReconciliationHandled(ctx, kubeClient, namespacedName, &kustomization, lastHandledReconcileAt),
); err != nil { ); err != nil {
return err return err
@@ -142,10 +155,10 @@ func requestKustomizeReconciliation(ctx context.Context, kubeClient client.Clien
} }
if kustomization.Annotations == nil { if kustomization.Annotations == nil {
kustomization.Annotations = map[string]string{ kustomization.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
} }
} else { } else {
kustomization.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) kustomization.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
} }
return kubeClient.Update(ctx, kustomization) return kubeClient.Update(ctx, kustomization)
}) })

View File

@@ -51,16 +51,16 @@ func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
@@ -74,13 +74,13 @@ func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("resource is suspended") return fmt.Errorf("resource is suspended")
} }
logger.Actionf("annotating Receiver %s in %s namespace", name, namespace) logger.Actionf("annotating Receiver %s in %s namespace", name, rootArgs.namespace)
if receiver.Annotations == nil { if receiver.Annotations == nil {
receiver.Annotations = map[string]string{ receiver.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano), meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
} }
} else { } else {
receiver.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano) receiver.Annotations[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
} }
if err := kubeClient.Update(ctx, &receiver); err != nil { if err := kubeClient.Update(ctx, &receiver); err != nil {
return err return err
@@ -88,7 +88,7 @@ func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("Receiver annotated") logger.Successf("Receiver annotated")
logger.Waitingf("waiting for Receiver reconciliation") logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil { isReceiverReady(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err return err
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, 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(
pollInterval, 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.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
bucket.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
return kubeClient.Update(ctx, bucket)
})
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, 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(pollInterval, 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.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
repository.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
return kubeClient.Update(ctx, repository)
})
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, 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(pollInterval, 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.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
repository.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
return kubeClient.Update(ctx, repository)
})
} }

View File

@@ -55,33 +55,33 @@ func (resume resumeCommand) run(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
err = kubeClient.Get(ctx, namespacedName, resume.object.asRuntimeObject()) err = kubeClient.Get(ctx, namespacedName, resume.object.asClientObject())
if err != nil { if err != nil {
return err return err
} }
logger.Actionf("resuming %s %s in %s namespace", resume.humanKind, name, namespace) logger.Actionf("resuming %s %s in %s namespace", resume.humanKind, name, rootArgs.namespace)
resume.object.setUnsuspended() resume.object.setUnsuspended()
if err := kubeClient.Update(ctx, resume.object.asRuntimeObject()); err != nil { if err := kubeClient.Update(ctx, resume.object.asClientObject()); err != nil {
return err return err
} }
logger.Successf("%s resumed", resume.humanKind) logger.Successf("%s resumed", resume.humanKind)
logger.Waitingf("waiting for %s reconciliation", resume.kind) logger.Waitingf("waiting for %s reconciliation", resume.kind)
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReady(ctx, kubeClient, namespacedName, resume.object)); err != nil { isReady(ctx, kubeClient, namespacedName, resume.object)); err != nil {
return err return err
} }

View File

@@ -54,16 +54,16 @@ func resumeAlertCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var alert notificationv1.Alert var alert notificationv1.Alert
@@ -72,7 +72,7 @@ func resumeAlertCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logger.Actionf("resuming Alert %s in %s namespace", name, namespace) logger.Actionf("resuming Alert %s in %s namespace", name, rootArgs.namespace)
alert.Spec.Suspend = false alert.Spec.Suspend = false
if err := kubeClient.Update(ctx, &alert); err != nil { if err := kubeClient.Update(ctx, &alert); err != nil {
return err return err
@@ -80,7 +80,7 @@ func resumeAlertCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("Alert resumed") logger.Successf("Alert resumed")
logger.Waitingf("waiting for Alert reconciliation") logger.Waitingf("waiting for Alert reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isAlertResumed(ctx, kubeClient, namespacedName, &alert)); err != nil { isAlertResumed(ctx, kubeClient, namespacedName, &alert)); err != nil {
return err return err
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, 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(pollInterval, 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
}
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, 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(pollInterval, 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
}
} }

View File

@@ -54,16 +54,16 @@ func resumeReceiverCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var receiver notificationv1.Receiver var receiver notificationv1.Receiver
@@ -72,7 +72,7 @@ func resumeReceiverCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logger.Actionf("resuming Receiver %s in %s namespace", name, namespace) logger.Actionf("resuming Receiver %s in %s namespace", name, rootArgs.namespace)
receiver.Spec.Suspend = false receiver.Spec.Suspend = false
if err := kubeClient.Update(ctx, &receiver); err != nil { if err := kubeClient.Update(ctx, &receiver); err != nil {
return err return err
@@ -80,7 +80,7 @@ func resumeReceiverCmdRun(cmd *cobra.Command, args []string) error {
logger.Successf("Receiver resumed") logger.Successf("Receiver resumed")
logger.Waitingf("waiting for Receiver reconciliation") logger.Waitingf("waiting for Receiver reconciliation")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReceiverResumed(ctx, kubeClient, namespacedName, &receiver)); err != nil { isReceiverResumed(ctx, kubeClient, namespacedName, &receiver)); err != nil {
return err return err
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, 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(pollInterval, 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
}
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, 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(pollInterval, 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
}
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, 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(pollInterval, 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
}
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, 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(pollInterval, 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
}
} }

143
cmd/flux/source.go Normal file
View File

@@ -0,0 +1,143 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"sigs.k8s.io/controller-runtime/pkg/client"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
)
// These are general-purpose adapters for attaching methods to, for
// the various commands. The *List adapters implement len(), since
// it's used in at least a couple of commands.
// sourcev1.Bucket
var bucketType = apiType{
kind: sourcev1.BucketKind,
humanKind: "source bucket",
}
type bucketAdapter struct {
*sourcev1.Bucket
}
func (a bucketAdapter) asClientObject() client.Object {
return a.Bucket
}
// sourcev1.BucketList
type bucketListAdapter struct {
*sourcev1.BucketList
}
func (a bucketListAdapter) asClientList() client.ObjectList {
return a.BucketList
}
func (a bucketListAdapter) len() int {
return len(a.BucketList.Items)
}
// sourcev1.HelmChart
var helmChartType = apiType{
kind: sourcev1.HelmChartKind,
humanKind: "source chart",
}
type helmChartAdapter struct {
*sourcev1.HelmChart
}
func (a helmChartAdapter) asClientObject() client.Object {
return a.HelmChart
}
// sourcev1.ImagePolicyList
type helmChartListAdapter struct {
*sourcev1.HelmChartList
}
func (a helmChartListAdapter) asClientList() client.ObjectList {
return a.HelmChartList
}
func (a helmChartListAdapter) len() int {
return len(a.HelmChartList.Items)
}
// sourcev1.GitRepository
var gitRepositoryType = apiType{
kind: sourcev1.GitRepositoryKind,
humanKind: "source git",
}
type gitRepositoryAdapter struct {
*sourcev1.GitRepository
}
func (a gitRepositoryAdapter) asClientObject() client.Object {
return a.GitRepository
}
// sourcev1.GitRepositoryList
type gitRepositoryListAdapter struct {
*sourcev1.GitRepositoryList
}
func (a gitRepositoryListAdapter) asClientList() client.ObjectList {
return a.GitRepositoryList
}
func (a gitRepositoryListAdapter) len() int {
return len(a.GitRepositoryList.Items)
}
// sourcev1.HelmRepository
var helmRepositoryType = apiType{
kind: sourcev1.HelmRepositoryKind,
humanKind: "source helm",
}
type helmRepositoryAdapter struct {
*sourcev1.HelmRepository
}
func (a helmRepositoryAdapter) asClientObject() client.Object {
return a.HelmRepository
}
// sourcev1.HelmRepositoryList
type helmRepositoryListAdapter struct {
*sourcev1.HelmRepositoryList
}
func (a helmRepositoryListAdapter) asClientList() client.ObjectList {
return a.HelmRepositoryList
}
func (a helmRepositoryListAdapter) len() int {
return len(a.HelmRepositoryList.Items)
}

View File

@@ -42,7 +42,7 @@ type statusable interface {
func isReady(ctx context.Context, kubeClient client.Client, func isReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, object statusable) wait.ConditionFunc { namespacedName types.NamespacedName, object statusable) wait.ConditionFunc {
return func() (bool, error) { return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, object.asRuntimeObject()) err := kubeClient.Get(ctx, namespacedName, object.asClientObject())
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@@ -53,26 +53,26 @@ func (suspend suspendCommand) run(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
err = kubeClient.Get(ctx, namespacedName, suspend.object.asRuntimeObject()) err = kubeClient.Get(ctx, namespacedName, suspend.object.asClientObject())
if err != nil { if err != nil {
return err return err
} }
logger.Actionf("suspending %s %s in %s namespace", suspend.humanKind, name, namespace) logger.Actionf("suspending %s %s in %s namespace", suspend.humanKind, name, rootArgs.namespace)
suspend.object.setSuspended() suspend.object.setSuspended()
if err := kubeClient.Update(ctx, suspend.object.asRuntimeObject()); err != nil { if err := kubeClient.Update(ctx, suspend.object.asClientObject()); err != nil {
return err return err
} }
logger.Successf("%s suspended", suspend.humanKind) logger.Successf("%s suspended", suspend.humanKind)

View File

@@ -47,16 +47,16 @@ func suspendAlertCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var alert notificationv1.Alert var alert notificationv1.Alert
@@ -65,7 +65,7 @@ func suspendAlertCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logger.Actionf("suspending Alert %s in %s namespace", name, namespace) logger.Actionf("suspending Alert %s in %s namespace", name, rootArgs.namespace)
alert.Spec.Suspend = true alert.Spec.Suspend = true
if err := kubeClient.Update(ctx, &alert); err != nil { if err := kubeClient.Update(ctx, &alert); err != nil {
return err return err

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, namespace)
helmRelease.Spec.Suspend = true
if err := kubeClient.Update(ctx, &helmRelease); err != nil {
return err
}
logger.Successf("HelmRelease suspended")
return nil
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, namespace)
kustomization.Spec.Suspend = true
if err := kubeClient.Update(ctx, &kustomization); err != nil {
return err
}
logger.Successf("kustomization suspended")
return nil
} }

View File

@@ -47,16 +47,16 @@ func suspendReceiverCmdRun(cmd *cobra.Command, args []string) error {
} }
name := args[0] name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: name, Name: name,
} }
var receiver notificationv1.Receiver var receiver notificationv1.Receiver
@@ -65,7 +65,7 @@ func suspendReceiverCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logger.Actionf("suspending Receiver %s in %s namespace", name, namespace) logger.Actionf("suspending Receiver %s in %s namespace", name, rootArgs.namespace)
receiver.Spec.Suspend = true receiver.Spec.Suspend = true
if err := kubeClient.Update(ctx, &receiver); err != nil { if err := kubeClient.Update(ctx, &receiver); err != nil {
return err return err

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, namespace)
bucket.Spec.Suspend = true
if err := kubeClient.Update(ctx, &bucket); err != nil {
return err
}
logger.Successf("source suspended")
return nil
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, namespace)
chart.Spec.Suspend = true
if err := kubeClient.Update(ctx, &chart); err != nil {
return err
}
logger.Successf("source suspended")
return nil
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, namespace)
repository.Spec.Suspend = true
if err := kubeClient.Update(ctx, &repository); err != nil {
return err
}
logger.Successf("source suspended")
return nil
} }

View File

@@ -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(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: 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, namespace)
repository.Spec.Suspend = true
if err := kubeClient.Update(ctx, &repository); err != nil {
return err
}
logger.Successf("source suspended")
return nil
} }

View File

@@ -45,38 +45,40 @@ var uninstallCmd = &cobra.Command{
RunE: uninstallCmdRun, RunE: uninstallCmdRun,
} }
var ( type uninstallFlags struct {
uninstallCRDs bool crds bool
uninstallResources bool resources bool
uninstallDryRun bool dryRun bool
uninstallSilent bool silent bool
) }
var uninstallArgs uninstallFlags
func init() { func init() {
uninstallCmd.Flags().BoolVar(&uninstallResources, "resources", true, uninstallCmd.Flags().BoolVar(&uninstallArgs.resources, "resources", true,
"removes custom resources such as Kustomizations, GitRepositories and HelmRepositories") "removes custom resources such as Kustomizations, GitRepositories and HelmRepositories")
uninstallCmd.Flags().BoolVar(&uninstallCRDs, "crds", false, uninstallCmd.Flags().BoolVar(&uninstallArgs.crds, "crds", false,
"removes all CRDs previously installed") "removes all CRDs previously installed")
uninstallCmd.Flags().BoolVar(&uninstallDryRun, "dry-run", false, uninstallCmd.Flags().BoolVar(&uninstallArgs.dryRun, "dry-run", false,
"only print the object that would be deleted") "only print the object that would be deleted")
uninstallCmd.Flags().BoolVarP(&uninstallSilent, "silent", "s", false, uninstallCmd.Flags().BoolVarP(&uninstallArgs.silent, "silent", "s", false,
"delete components without asking for confirmation") "delete components without asking for confirmation")
rootCmd.AddCommand(uninstallCmd) rootCmd.AddCommand(uninstallCmd)
} }
func uninstallCmdRun(cmd *cobra.Command, args []string) error { func uninstallCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }
if !uninstallDryRun && !uninstallSilent { if !uninstallArgs.dryRun && !uninstallArgs.silent {
prompt := promptui.Prompt{ prompt := promptui.Prompt{
Label: fmt.Sprintf("Are you sure you want to delete the %s namespace", namespace), Label: fmt.Sprintf("Are you sure you want to delete the %s namespace", rootArgs.namespace),
IsConfirm: true, IsConfirm: true,
} }
if _, err := prompt.Run(); err != nil { if _, err := prompt.Run(); err != nil {
@@ -85,7 +87,7 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
} }
dryRun := "--dry-run=server" dryRun := "--dry-run=server"
deleteResources := uninstallResources || uninstallCRDs deleteResources := uninstallArgs.resources || uninstallArgs.crds
// known kinds with finalizers // known kinds with finalizers
namespacedKinds := []string{ namespacedKinds := []string{
@@ -96,8 +98,8 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
// suspend bootstrap kustomization to avoid finalizers deadlock // suspend bootstrap kustomization to avoid finalizers deadlock
kustomizationName := types.NamespacedName{ kustomizationName := types.NamespacedName{
Namespace: namespace, Namespace: rootArgs.namespace,
Name: namespace, Name: rootArgs.namespace,
} }
var kustomization kustomizev1.Kustomization var kustomization kustomizev1.Kustomization
err = kubeClient.Get(ctx, kustomizationName, &kustomization) err = kubeClient.Get(ctx, kustomizationName, &kustomization)
@@ -113,21 +115,21 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
// add HelmRelease kind to deletion list if exists // add HelmRelease kind to deletion list if exists
var list helmv2.HelmReleaseList var list helmv2.HelmReleaseList
if err := kubeClient.List(ctx, &list, client.InNamespace(namespace)); err == nil { if err := kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace)); err == nil {
namespacedKinds = append(namespacedKinds, helmv2.HelmReleaseKind) namespacedKinds = append(namespacedKinds, helmv2.HelmReleaseKind)
} }
if deleteResources { if deleteResources {
logger.Actionf("uninstalling custom resources") logger.Actionf("uninstalling custom resources")
for _, kind := range namespacedKinds { for _, kind := range namespacedKinds {
if err := deleteAll(ctx, kind, uninstallDryRun); err != nil { if err := deleteAll(ctx, kind, uninstallArgs.dryRun); err != nil {
logger.Failuref("kubectl: %s", err.Error()) logger.Failuref("kubectl: %s", err.Error())
} }
} }
} }
var kinds []string var kinds []string
if uninstallCRDs { if uninstallArgs.crds {
kinds = append(kinds, "crds") kinds = append(kinds, "crds")
} }
@@ -138,13 +140,13 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
for _, kind := range kinds { for _, kind := range kinds {
kubectlArgs := []string{ kubectlArgs := []string{
"delete", kind, "delete", kind,
"-l", fmt.Sprintf("app.kubernetes.io/instance=%s", namespace), "-l", fmt.Sprintf("app.kubernetes.io/instance=%s", rootArgs.namespace),
"--ignore-not-found", "--timeout", timeout.String(), "--ignore-not-found", "--timeout", rootArgs.timeout.String(),
} }
if uninstallDryRun { if uninstallArgs.dryRun {
kubectlArgs = append(kubectlArgs, dryRun) kubectlArgs = append(kubectlArgs, dryRun)
} }
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubeconfig, kubecontext, kubectlArgs...); err != nil { if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil {
return fmt.Errorf("uninstall failed: %w", err) return fmt.Errorf("uninstall failed: %w", err)
} }
} }
@@ -157,13 +159,13 @@ func deleteAll(ctx context.Context, kind string, dryRun bool) error {
kubectlArgs := []string{ kubectlArgs := []string{
"delete", kind, "--ignore-not-found", "delete", kind, "--ignore-not-found",
"--all", "--all-namespaces", "--all", "--all-namespaces",
"--timeout", timeout.String(), "--timeout", rootArgs.timeout.String(),
} }
if dryRun { if dryRun {
kubectlArgs = append(kubectlArgs, "--dry-run=server") kubectlArgs = append(kubectlArgs, "--dry-run=server")
} }
_, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubeconfig, kubecontext, kubectlArgs...) _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
return err return err
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

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