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

Compare commits

..

436 Commits

Author SHA1 Message Date
Stefan Prodan
30008de400 Merge pull request #867 from fluxcd/get-resource-by-name
Add support for getting resources by name
2021-02-05 17:24:50 +02:00
Stefan Prodan
a5fa731545 Add support for getting resources by name
- add singular alias to get commands
- allow filtering the get commands result by resource name
- add the image commands to mkdocs index

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-02-05 16:35:23 +02:00
Stefan Prodan
493ee3c956 Merge pull request #866 from fluxcd/hr-values
Add support for multiple values files to create hr
2021-02-05 16:09:29 +02:00
Stefan Prodan
3dd574ee51 Add support for multiple values files to create hr
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-02-05 15:43:03 +02:00
Hidde Beydals
5416c19b2e Merge pull request #863 from fluxcd/update-git-pkg 2021-02-05 14:42:32 +01:00
Hidde Beydals
2f31d80c7a Update git from fluxcd/pkg
This incorporates the changes made to the GitLab provider.

This means that we no longer rely on UI names, but rather use the unique
path identifier (the elements you see in your address bar when looking
at e.g. a group in your GitLab environment).

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-02-05 13:38:49 +01:00
Stefan Prodan
27d1833854 Merge pull request #848 from ViBiOh/patch-1
Exclude deleted resources on prometheus alerting query
2021-02-05 09:28:49 +02:00
Vincent Boutour
84ed716908 Exclude deleted resources on prometheus alerting query
Signed-off-by: Vincent Boutour <bob@vibioh.fr>
2021-02-04 18:10:42 +01:00
Michael Bridgen
6c9c9c7578 Merge pull request #790 from fluxcd/certs-for-imagerepo
Give image repository a cert-secret-ref flag
2021-02-04 13:13:43 +00:00
Michael Bridgen
cc7b7b0689 Give examples of create image repository
Signed-off-by: Michael Bridgen <michael@weave.works>
2021-02-04 12:55:42 +00:00
Michael Bridgen
5df8e05d1a Give image repository a cert-secret-ref flag
ImageRepository objects can now refer to a secret containing
certificates to use for TLS. This adds the flag

    flux create image repository --cert-secret-ref

for naming a secret to use. You can create such a secret with

    flux create secret tls

Signed-off-by: Michael Bridgen <michael@weave.works>
2021-02-04 12:55:42 +00:00
Michael Bridgen
b3b224b0ca Merge pull request #862 from fluxcd/correct-image-delete
Rename flux delete auto to flux delete image
2021-02-04 12:54:05 +00:00
Michael Bridgen
75ab28ee5d Rename flux delete auto to flux delete image
This slipped through the auto->image change made in the course of
preparing #538.

Signed-off-by: Michael Bridgen <michael@weave.works>
2021-02-04 11:57:40 +00:00
Michael Bridgen
aa9ea2b4ab Merge pull request #843 from fluxcd/create-image-update-typo
Correct spelling of repository in error
2021-02-04 10:46:27 +00:00
Michael Bridgen
1e6be99c36 Correct spelling of repository in error
Signed-off-by: Michael Bridgen <michael@weave.works>
2021-02-04 10:16:09 +00:00
Stefan Prodan
49fb396bf8 Merge pull request #861 from fluxcd/refactor-checks
Refactor components status check
2021-02-04 11:56:22 +02:00
Stefan Prodan
e055c9ddc1 Refactor components status check
- run install/bootstrap checks in parallel (1m timeout)
- list not found components

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2021-02-04 11:16:27 +02:00
Stefan Prodan
c708e390a7 Merge pull request #845 from jonathan-innis/jonathan-innis/kstatus
Replace kubectl rollout with kstatus checks
2021-02-04 09:11:41 +02:00
jonathan-innis
d5ad26c934 Change failed message for bootstrap
Signed-off-by: jonathan-innis <jonathan.innis.ji@gmail.com>
2021-02-03 12:08:10 -08:00
jonathan-innis
144b7cd922 Update errors returned to user
Signed-off-by: jonathan-innis <jonathan.innis.ji@gmail.com>
2021-02-03 12:07:29 -08:00
jonathan-innis
9e86fbb311 Tidy up the mod imports
Signed-off-by: jonathan-innis <jonathan.innis.ji@gmail.com>
2021-02-03 12:07:29 -08:00
jonathan-innis
b528428d02 Add kstatus to install and check commands
Signed-off-by: jonathan-innis <jonathan.innis.ji@gmail.com>
2021-02-03 12:07:29 -08:00
jonathan-innis
b3d7730e79 Use status polling in bootstrap command
Signed-off-by: jonathan-innis <jonathan.innis.ji@gmail.com>
2021-02-03 12:07:19 -08:00
Hidde Beydals
f2ba567ca4 Merge pull request #857 from fluxcd/update-components
Update toolkit components
2021-02-03 19:34:43 +01:00
fluxcdbot
8342f77087 Update toolkit components
- source-controller to v0.7.4
  https://github.com/fluxcd/source-controller/blob/v0.7.4/CHANGELOG.md

Signed-off-by: GitHub <noreply@github.com>
2021-02-03 14:51:22 +00:00
Hidde Beydals
7cade1b98f Merge pull request #858 from fluxcd/component-update-cfg
Put CHANGELOG URL on new line in commit / PR body
2021-02-03 15:50:47 +01:00
Hidde Beydals
ee4c1fb36c Put CHANGELOG URL on new line in commit / PR body
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-02-03 15:22:57 +01:00
Hidde Beydals
dbc4e537fe Merge pull request #854 from fluxcd/move-migration-menu 2021-02-03 13:10:59 +01:00
Hidde Beydals
e28990b96c Move migration sub-menu to top-menu
Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-02-03 11:49:51 +01:00
Hidde Beydals
408cf92c04 Merge pull request #853 from fluxcd/component-update-cfg
Tune component update configuration
2021-02-03 10:22:58 +01:00
Hidde Beydals
425af2e0dc Tune component update configuration
- Include link to changelog of component in commit and PR message
- Label pull request automatically with `area/build`
- Enable sign-off of commits to free us from manual labour

Signed-off-by: Hidde Beydals <hello@hidde.co>
2021-02-02 18:48:49 +01:00
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
Hidde Beydals
dd5165dcbf Merge pull request #624 from fluxcd/update-components
Update toolkit components
2020-12-18 15:45:00 +01:00
fluxcdbot
6da22613fe Update toolkit components 2020-12-18 14:36:18 +00:00
Hidde Beydals
d0926776a5 Merge pull request #627 from fluxcd/doc-targetpath-escape-note
docs: add note about `TargetPath` and JSON values
2020-12-18 15:35:13 +01:00
Hidde Beydals
14dc39e8d2 docs: add note about TargetPath and JSON values
Signed-off-by: Hidde Beydals <hello@hidde.co>
2020-12-18 15:12:44 +01:00
Hidde Beydals
f0f2a79384 Merge pull request #622 from vterdunov/patch-1 2020-12-18 14:19:19 +01:00
Hidde Beydals
7b6f875920 docs: 'like so' -> 'as in the following example'
Signed-off-by: Hidde Beydals <hello@hidde.co>
2020-12-18 14:00:52 +01:00
Terdunov Vyacheslav
52cec044b8 Fix typo
Signed-off-by: Terdunov Vyacheslav <mail2slick@gmail.com>
2020-12-18 14:00:52 +01:00
Hidde Beydals
07dd59892f Merge pull request #623 from fluxcd/e2e-timeout
Set e2e uninstall timeout to 10m
2020-12-18 14:00:29 +01:00
Stefan Prodan
ffeaa683c5 Set e2e uninstall timeout to 10m
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-18 13:50:15 +01:00
Hidde Beydals
1301bf7c15 Merge pull request #617 from alexei-led/main
Support check command with multiple config files
2020-12-18 12:13:12 +01:00
Alexei Ledenev
69387fd2a4 Support check command with multiple config files
Resolves: #472
Signed-off-by: Alexei Ledenev <alexei.led@gmail.com>
2020-12-18 12:13:57 +02:00
Stefan Prodan
12a0ebe3ba Merge pull request #619 from fluxcd/pat-hint
Add note about deploy key linked to pat
2020-12-17 16:20:57 +02:00
Philip Laine
3de81827eb Move hint to github guide
Signed-off-by: Philip Laine <philip.laine@xenit.se>
2020-12-17 14:57:55 +01:00
Philip Laine
a7362b60e7 Add note about deploy key linked to pat
Signed-off-by: Philip Laine <philip.laine@xenit.se>
2020-12-17 14:54:08 +01:00
Stefan Prodan
5d4bb3a43f Merge pull request #618 from fluxcd/kustomize-helm-e2e
Add e2e test for flux2-kustomize-helm-example
2020-12-17 15:50:10 +02:00
Stefan Prodan
d02d507812 Add e2e test for flux2-kustomize-helm-example
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-17 14:57:47 +02:00
Stefan Prodan
554de7ba6f Merge pull request #616 from fluxcd/fix-action-binary
Move flux binary to GitHub workspace
2020-12-17 14:42:42 +02:00
Stefan Prodan
5d9ccc973d Move flux binary to GitHub workspace
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-17 14:26:35 +02:00
Stefan Prodan
53ffb8aa00 Merge pull request #615 from fluxcd/flux-action-docs-ignore
Add note about ignoring flux action binary
2020-12-17 14:16:43 +02:00
Stefan Prodan
c4da4a81aa Add note about ignoring flux action binary
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-17 13:34:39 +02:00
Hidde Beydals
b824ea8858 Merge pull request #612 from fluxcd/docs/zsh-completion-example
Fix zsh completion command example
2020-12-17 09:48:46 +01:00
Hidde Beydals
22e26efec1 Fix zsh completion command example
Signed-off-by: Hidde Beydals <hello@hidde.co>
2020-12-16 21:15:41 +01:00
Hidde Beydals
679490e8f4 Merge pull request #610 from fluxcd/update-components
Update toolkit components
2020-12-16 17:25:33 +01:00
fluxcdbot
15f17ed36d Update toolkit components 2020-12-16 16:12:25 +00:00
Hidde Beydals
c8265fb80c Merge pull request #607 from fluxcd/docs-fix-image-updates-branch
Add branch to image automation docs
2020-12-15 12:19:28 +01:00
Stefan Prodan
3883e92631 Add branch to image automation docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-15 12:23:55 +02:00
Stefan Prodan
aa9bc4ce8b Merge pull request #606 from fluxcd/update-components
Update kustomize-controller to v0.5.1
2020-12-14 19:10:30 +02:00
fluxcdbot
37c14e8088 Update toolkit components 2020-12-14 16:58:51 +00:00
Hidde Beydals
439fbafc01 Merge pull request #605 from fluxcd/case-insensitive-selector-args
Make resource selector args case insensitive
2020-12-14 17:50:46 +01:00
Hidde Beydals
1b8e980519 Make resource selector args case insensitive
So that `<kind>/<name>` flags can be supplied as:

* `secret/foo`
* `Secret/foo`
* `SeCrEt/foo`

But result in: `Secret/foo`.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2020-12-14 17:39:25 +01:00
Hidde Beydals
567acb6291 Merge pull request #604 from fluxcd/flags-tests
Add tests for CLI flags
2020-12-14 16:57:55 +01:00
Hidde Beydals
996bfe87ff Add tests for CLI flags
This includes various bug fixes, especially around the area of missing
names for `<kind>/<name>` formats.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2020-12-14 16:47:46 +01:00
Hidde Beydals
3c1793b6c5 Merge pull request #603 from fluxcd/fix-azure-devops-docs 2020-12-14 16:47:24 +01:00
Stefan Prodan
1a7f253767 Fix Azure DevOps SSH URL in docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-14 17:40:40 +02:00
Hidde Beydals
f188e59b21 Merge pull request #602 from fluxcd/safe-rel-path
Add safe guards for relative paths
2020-12-14 15:23:13 +01:00
Hidde Beydals
5ea4e814f5 Add safe guards for relative paths
This commit adds multiple safe guards for relative paths, ensuring they
never traverse outside the working directory.

The `SafeRelativePath` flag calculates the safe relative path based on a
relative base dir, which results in a flattened path.

The write methods of `manifestgen` make use of the `SecureJoin` as well,
to ensure writes are never outside of the given directory when used as
a lib outside of the CLI.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2020-12-14 15:14:49 +01:00
Stefan Prodan
008b3b8408 Merge pull request #599 from fluxcd/docs-image-updates
Add image automation guide
2020-12-14 12:35:11 +02:00
Stefan Prodan
7ae3dee900 Add image automation guide
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-14 11:19:03 +02:00
Stefan Prodan
2395ab6e14 Merge pull request #597 from fluxcd/fix-cluster-domain
Fix cluster domain mapping
2020-12-13 16:48:59 +02:00
Stefan Prodan
8efe053ffa Fix cluster domain mapping
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-13 16:35:19 +02:00
Stefan Prodan
612600b88c Merge pull request #595 from L3o-pold/add-cluster-domain-bootstrap-option
Add cluster-domain option for bootstrap command
2020-12-12 17:46:17 +02:00
Léopold Jacquot
4d7df52dbe Add cluster-domain option for bootstrap command
Signed-off-by: Léopold Jacquot <leopold.jacquot@infomaniak.com>
2020-12-12 16:37:05 +01:00
Stefan Prodan
b6c63a1aa4 Merge pull request #594 from fluxcd/update-components
Update source-controller to v0.5.4
2020-12-12 16:38:50 +02:00
fluxcdbot
a4788ce6bb Update toolkit components 2020-12-12 13:11:45 +00:00
Michael Bridgen
0ba6fc1b36 Merge pull request #538 from fluxcd/image-controller-commands
Add commands for image automation API
2020-12-11 16:45:36 +00:00
Michael Bridgen
0e35c209d9 Factor out upsert and upsertAndWait
It's a common pattern in the create commands to construct a value,
then (if not exporting it) upsert it and wait for it to
reconcile. This commit factors `upsert`, which does the update/insert
bit, and `upsertAndWait`, which does the whole thing.

Since these output messages, they are methods of `apiType` (previously
`names`), so that they have access to the name of the kind they are
operating on.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
3b9b2cbe9f Reuse isReady from create_image commands
I implemented the isReady procedure for adapters for resume -- use it
in create too.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
45240bdb71 Rename "auto" subcommands to "image"
This means all the sub-subcommands can drop the `image-` prefix,
making them shorter and more fluent.

E.g.,

    flux create image policy

rather than

    flux create auto image-policy

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
22a5ac7f0f Standardise the names of types
Most commands use either a kind, or a more readable spelling of a
kind, in their output. To make this easier, this centralises the
definition of those names in one place, and lets the command
implementations choose whichever they need.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
d55d185044 Implement suspend, resume, reconcile image-update
.. and refactor. These are all amenable to the adapter refactoring
that has served well so far.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
2bb09697ce Centralise adapter types
Since the generic commands tend to share a few of the methods they
need -- at least AsClientObject -- it's worth having just one wrapper
struct for each API type, and adding methods to it where necessary.

For the automation types, I put these in auto.go.

While doing this I also did some tidying:

 - I changed the name of the wrappers to `<type>Adapter`, and the
   generic adapter to `universalAdapter` (it's only needed for delete,
   so far).

 - I de-exported and renamed some interface methods e.g.,
   `exportItem`. They aren't needed outside the package.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
f316aff2d3 Add delete, export, get image-update
This uses the established abstractions to implement the usual
subcommands for the ImageUpdateAutomation type.

I've called the sub-subcommand in each case `image-update`, as a
fairly safe shorthand for the much longer `image-update-automation`.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
433628791b Add create auto image-update command
This adds the create subcommand, without attempting any refactoring.

NB the TODO: the image/v1alpha1 API does not yet export a const for
the name of the kind. The field `RunInterval` will likely be changed
to `Interval` (with a value field), at some point, too.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
4f52b77563 Factor out export command control flow
The export command works the same way for most (all?) types. I have
made it generic and moved it into export.go, then ported
{export,create}_auto_image{repository,policy}.go to use it.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
52145c045d Add delete image-policy and refactor
This adds a command for deleting ImagePolicy objects. Since the
control flow for the command needs only a runtime.Object (and a name
for the type), it can be factored out.

I have made the argument (field in the deleteCommand struct) an
interface `objectContainer`, through which the command code gets a
`runtime.Object` to deserialise into (and delete). It could be simply
a `runtime.Object` here; however things like `getCommand` require
other methods, so it's convenient to have an interface for it.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
512761080e Add get auto image-policy and refactor
This factors the get command implementation so that the control flow
is generic and relies on a handful of methods, then uses that to add
`get auto image-policy` and to rewrite `get auto image-repository`.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
037a5b71fd Add {create,export} auto image-policy
Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:18 +00:00
Michael Bridgen
b66bdec61a Add subcommands for image-repository
This adds all the standard subcommands for the ImageRepository type.

Following `source`, I have put them under a namespace: `auto`,
referring to automation.

NB For `create` I use controllerutil.CreateOrUpdate, which looks to me
like a slightly more rounded version of the upsert* funcs.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 16:34:14 +00:00
Hidde Beydals
16f52610ab Merge pull request #589 from fluxcd/update-components
Update source-controller to v0.5.3
2020-12-11 17:14:49 +01:00
fluxcdbot
b2f018e29b Update toolkit components 2020-12-11 16:07:13 +00:00
Michael Bridgen
1f497cac44 Merge pull request #580 from fluxcd/install-image-controllers
Include image controller config in `flux install`
2020-12-11 15:22:51 +00:00
Michael Bridgen
4abe69f90a Give flux bootstrap the extra components flag
This commit adds a flag for supplying extra components to bootstrap
(and its subcommands), to match the one for `flux install`.

Since the bootstrapComponents global is used in a few places, I made
it a func and renamed the variable. For consistency, I also renamed
the var used in install.go.

Lastly, so that the flag sorts next to `--components`, I changed it to
`--components-extra` in both commands.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 15:08:19 +00:00
Michael Bridgen
75023011d3 Add argument for adding to default install
If you want to install the default set of controllers and the image-*
controllers, at present you have to list every single one of them.

An improvement on this is to let people specify what they want _in
addition_ to the default controllers. This commit adds an argument
`--extra-components` which appends to the (most likely, default value)
slice of `--components`.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 15:08:19 +00:00
Michael Bridgen
09f145d880 Add kustomizations for the image-* controllers
I have used the cookie-cutter from the examples already there.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 15:08:19 +00:00
Michael Bridgen
811cd4248f Include image-* controllers in update workflow
Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-11 15:08:19 +00:00
Hidde Beydals
a4871724ac Merge pull request #587 from fluxcd/update-components
Update source-controller to v0.5.2
2020-12-11 15:24:35 +01:00
fluxcdbot
a7d6446d8f Update toolkit components 2020-12-11 14:17:20 +00:00
Stefan Prodan
635a17ef1e Merge pull request #586 from fluxcd/kustomize/api-v0.7.0
Update kustomize/api to v0.7.0
2020-12-11 16:16:10 +02:00
Stefan Prodan
6280fbce17 Update kustomize/api to v0.7.0
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-11 16:07:23 +02:00
Stefan Prodan
daa72e72b0 Merge pull request #585 from fluxcd/update-components
Update kustomize-controller to v0.5.0
2020-12-11 15:30:38 +02:00
fluxcdbot
35bb770697 Update toolkit components 2020-12-11 13:19:36 +00:00
Stefan Prodan
9cc5a7d8de Merge pull request #584 from fluxcd/docs-azure-devops
Rearrange Azure DevOps docs
2020-12-11 15:12:54 +02:00
Stefan Prodan
9b62f01b53 Rearrange Azure DevOps docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-11 14:52:19 +02:00
Stefan Prodan
a643a82006 Merge pull request #566 from fluxcd/feature/libgit2
Add git implementation flag and note about Azure DevOps
2020-12-11 13:58:53 +02:00
Philip Laine
82b74d8689 Add git implementation flag and note about Azure DevOps
Signed-off-by: Philip Laine <philip.laine@gmail.com>
2020-12-11 12:10:30 +01:00
Hidde Beydals
a5825bb9f5 Merge pull request #581 from fluxcd/stdlogger
Log to stderr
2020-12-11 10:34:40 +01:00
Hidde Beydals
88a890d717 Log to stderr
This commit refactors the `printLogger` into a `stderrLogger` that
properly logs to `os.stderr` instead of `os.stdout`.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2020-12-10 20:21:09 +01:00
Hidde Beydals
be6fab795d Merge pull request #578 from fluxcd/rel-base-path 2020-12-10 18:51:41 +01:00
Hidde Beydals
7a5b9e2991 Use rel filepath in auto generated kustomization
This works around another bug on Windows platforms that would cause the
kustomize-controller to choke on the kustomization.yaml generated by the
bootstrap command due to the filepath being in a Windows format.

By using `filepath.Rel`, the output is _just_ the filename for files
relative to the bootstrap path, which is at the moment sufficient to
make it work.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2020-12-10 18:37:01 +01:00
Hidde Beydals
ee1f70841c Use path rel to working dir for kustomize build
Work around for a bug in kustomize causing it to not properly
handle absolute paths on Windows.

Signed-off-by: Hidde Beydals <hello@hidde.co>
2020-12-10 16:54:19 +01:00
Stefan Prodan
adc3d17eab Merge pull request #577 from fluxcd/update-git-pkg
Update fluxcd/pkg/git to v0.1.0
2020-12-10 15:05:16 +02:00
Stefan Prodan
f909d6fde2 Update fluxcd/pkg/git to v0.1.0
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-10 14:11:16 +02:00
Stefan Prodan
016a388147 Merge pull request #573 from fluxcd/update-components
Update toolkit components
2020-12-10 13:27:01 +02:00
fluxcdbot
aea442e7e1 Update toolkit components 2020-12-10 10:50:11 +00:00
Hidde Beydals
bb013ceb28 Merge pull request #576 from sylr/fix-typo
Fix typo in manifests download error message
2020-12-10 11:13:26 +01:00
Sylvain Rabot
dd65e9b89d Fix typo
Signed-off-by: Sylvain Rabot <sylvain@abstraction.fr>
2020-12-10 11:00:54 +01:00
Stefan Prodan
12146eda8c Merge pull request #569 from fluxcd/fix-https-auth
Fix create secret for Git over HTTP/S
2020-12-09 18:15:49 +02:00
Stefan Prodan
cd87fbba0d Fix create secret for Git over HTTP/S
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-09 17:39:04 +02:00
Stefan Prodan
c73541f81f Merge pull request #567 from fluxcd/docs-kustomize-faq
Add Kustomize FAQ to docs
2020-12-09 12:03:49 +02:00
Stefan Prodan
4618998792 Add Kustomize FAQ to docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-09 11:27:25 +02:00
Stefan Prodan
0a3b581aa9 Merge pull request #565 from mewzherder/patch-7
Upcoming events and Featured talks: reorder & update
2020-12-09 08:58:31 +02:00
mewzherder
aaa319b9bf Upcoming events and Featured talks: reorder & update
Signed-off-by: mewzherder <tamao@weave.works>
2020-12-08 08:59:36 -08:00
Hidde Beydals
25e782177b Merge pull request #559 from fluxcd/prevent-aur-publish-parallel-run
Prevent AUR package publishing parallel execution
2020-12-07 14:13:32 +01:00
Aurel Canciu
e940fd3d1f Prevent AUR package publishing parallel execution
Using a lock to prevent parallel executions in GoReleaser's custom
publishers. The custom publisher logic executes the tasks in parallel
for each available artifact.

https://goreleaser.com/customization/publishers/#how-it-works

Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-07 10:29:17 +02:00
Hidde Beydals
31d5cb4ad8 Merge pull request #557 from fluxcd/build/tmp-dir-rm
Properly clean-up package build dirs
2020-12-04 21:57:26 +01:00
Hidde Beydals
21576fe459 Properly clean-up package build dirs
Signed-off-by: Hidde Beydals <hello@hidde.co>
2020-12-04 21:49:41 +01:00
Hidde Beydals
65863a2cb8 Merge pull request #556 from fluxcd/fix-goreleaser-aur-publish
Use mock archive for aur publishers
2020-12-04 21:19:47 +01:00
Aurel Canciu
cdd055bfa6 Use mock archive for aur publishers
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 22:10:52 +02:00
Hidde Beydals
fedf960a5f Merge pull request #555 from fluxcd/fix-goreleaser-aur-publish
Remove `ids` GoReleaser attr in AUR pkg publisher
2020-12-04 20:31:31 +01:00
Aurel Canciu
4546fa3270 Remove ids GoReleaser attr in AUR pkg publisher
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 21:22:13 +02:00
Hidde Beydals
979f3f557c Merge pull request #554 from fluxcd/fix-goreleaser-aur-publish
Fix GoReleaser AUR publish concurrent exec issue
2020-12-04 20:08:08 +01:00
Aurel Canciu
48a38a8a5d Fix GoReleaser AUR publish concurrent exec issue
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 20:59:55 +02:00
Stefan Prodan
9880b32b0a Merge pull request #553 from fluxcd/fix-goreleaser-aur-publish
Fix GoReleaser AUR package publishing
2020-12-04 20:17:50 +02:00
Aurel Canciu
e664ef7a8d Fix GoReleaser AUR package publishing
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 20:08:32 +02:00
Stefan Prodan
7cfef379d0 Merge pull request #552 from fluxcd/fix-workflow-syntax-error
Fix GitHub Actions release workflow syntax error
2020-12-04 19:46:13 +02:00
Aurel Canciu
093a91c7fc Fix syntax error introduced earlier
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 19:36:01 +02:00
Hidde Beydals
94687a047f Merge pull request #551 from fluxcd/update-components
Update helm-controller to v0.4.2
2020-12-04 18:04:30 +01:00
fluxcdbot
38fdc603ad Update toolkit components 2020-12-04 16:52:10 +00:00
Stefan Prodan
55cecb7f96 Merge pull request #550 from fluxcd/install-with-kubectl
Publish install manifest to GitHub releases
2020-12-04 18:51:01 +02:00
Stefan Prodan
32e949598e Publish install manifest to GitHub releases
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-04 18:33:26 +02:00
Stefan Prodan
6d0c8aff4b Merge pull request #549 from fluxcd/add-priv-key-secret-for-goreleaser
Add AUR_BOT_SSH_PRIVATE_KEY env var for goreleaser
2020-12-04 17:58:54 +02:00
Aurel Canciu
5eecf03af6 Add AUR_BOT_SSH_PRIVATE_KEY env var for goreleaser
The AUR_BOT_SSH_PRIVATE_KEY environment variable needs to be set in
goreleaser so publishing the packages to AUR can work.

Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 17:49:25 +02:00
Stefan Prodan
76e9884032 Merge pull request #547 from fluxcd/create-secret-git
Add create secret git command
2020-12-04 17:39:08 +02:00
Stefan Prodan
9867c4baf0 Add create secret git command
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-04 16:54:13 +02:00
Stefan Prodan
2bc05c8cbd Merge pull request #548 from fluxcd/fix-md-aur-docs
Fix list parsing issue in the docs
2020-12-04 16:53:40 +02:00
Aurel Canciu
d15b0107e4 Fix list parsing issue in the docs
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 16:31:18 +02:00
Aurel Canciu
c64cb1304d Merge pull request #532 from fluxcd/aur-publish
Automated AUR publishing
2020-12-04 16:10:15 +02:00
Aurel Canciu
c1f209c7a5 Add information about the AUR packages to docs
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 16:01:33 +02:00
Aurel Canciu
116ccd6b3b Rename flux-git to flux-scm to prevent collision
A flux-git package already exists in AUR

Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 16:01:32 +02:00
Aurel Canciu
b6f30ae3e1 Move aur package templates to .github/aur
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 16:01:32 +02:00
Aurel Canciu
5c522ed2e1 Add publisher scripts and gorelease config
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 16:01:32 +02:00
Aurel Canciu
bc29b80912 Add PKGBUILD templates
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-12-04 16:01:32 +02:00
Stefan Prodan
cfbc17fbf8 Merge pull request #546 from fluxcd/label-secrets
Add labels to generated secrets
2020-12-04 13:46:42 +02:00
Stefan Prodan
af0c939302 Add labels to generated secrets
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-04 13:15:37 +02:00
Michael Bridgen
e02538d38d Merge pull request #441 from fluxcd/component-readiness-indication
Show the roadmap status more prominently and precisely
2020-12-03 16:32:46 +00:00
Michael Bridgen
001d37567c Provide pointers to the install guides in roadmap
This gives people a way into the software, alongside the idea of how
ready it is.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-03 16:22:56 +00:00
Michael Bridgen
af82ce31a6 Bring image automation roadmap up to date
Specifically,

 - using credentials from a secret is done
 - the CLI integration is underway

I gave the %-complete a decent bump to reflect those, and all the work
on making the image-* controllers have all the GOTK dials and knobs
e.g., suspend.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-03 16:09:27 +00:00
Michael Bridgen
12ad4908fa Separate out "scans at all" from authentication
This makes it a clearer that the component does something worthwhile,
and is lacking mainly in platform-specific support.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-12-03 16:06:22 +00:00
Hidde Beydals
40ef94ab45 Merge pull request #522 from vfarcic/main
Add link to video review from Viktor Farcic
2020-12-01 14:37:22 +01:00
Viktor Farcic
8834ab0210 Video
Signed-off-by: Viktor Farcic <viktor@farcic.com>
2020-12-01 14:24:45 +01:00
Stefan Prodan
128d23720f Merge pull request #535 from fluxcd/gh-action-auto-updates
Automate Flux upgrades with GitHub Actions
2020-12-01 12:10:36 +02:00
Stefan Prodan
90f4891ca9 Automate Flux upgrades with GitHub Actions
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-01 11:16:26 +02:00
Stefan Prodan
61ac81c4d9 Merge pull request #534 from fluxcd/docs-bootstrap
Specify where to place Kubernetes manifests after bootstrap
2020-12-01 10:30:01 +02:00
Stefan Prodan
bd05a8173c Specify where to place Kubernetes manifests after bootstrap
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-12-01 09:46:28 +02:00
Hidde Beydals
e3d6461a80 Merge pull request #528 from fluxcd/update-components
Update helm-controller to v0.4.1
2020-11-30 13:21:29 +01:00
fluxcdbot
2bb582f7ed Update toolkit components 2020-11-30 12:10:44 +00:00
Stefan Prodan
2f9a52852f Merge pull request #527 from fluxcd/tenant-e2e
Add e2e test for create tenant
2020-11-30 12:25:31 +02:00
Stefan Prodan
137f083b4d Add e2e test for create tenant
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-30 12:16:01 +02:00
Stefan Prodan
11f4c54a40 Merge pull request #525 from fluxcd/fixes
Fix tenant and reconcile commands
2020-11-30 11:46:36 +02:00
Stefan Prodan
c813eaf6d1 Do not try to reconcile a suspended object
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-30 11:39:55 +02:00
Stefan Prodan
ffdaa9dfe9 Fix tenant service account binding
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-30 10:28:03 +02:00
Stefan Prodan
182928002b Merge pull request #526 from phillebaba/add-maintainer
Add Philip Laine to maintainer list
2020-11-30 10:27:29 +02:00
Philip Laine
7222af2b7e Add Philip Laine to maintainer list
Signed-off-by: Philip Laine <philip.laine@xenit.se>
2020-11-29 19:17:51 +01:00
Stefan Prodan
034ead5272 Merge pull request #521 from fluxcd/improve-install-script-arch-detection
Improve installer list match for arm arches
2020-11-29 14:19:48 +02:00
Aurel Canciu
eca1f19e95 Improve installer list match for arm arches
`uname -m` will print out architecture codenames based on UTS_MACHINE
and COMPAT_UTS_MACHINE kernel defined constants. These extra values will
ensure the right version of the arm binary is installed on most Linux
systems running on ARM CPUs.

Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-11-27 19:38:06 +02:00
Michael Bridgen
ec70c14649 Merge pull request #520 from fluxcd/fluxv2-maintainers
Note the shared Flux v2 maintainers and team
2020-11-27 16:05:07 +00:00
Michael Bridgen
65d906a735 Note the shared Flux v2 maintainers and team
Many of the GitOps Toolkit controllers will share maintainers with
this repo, acting as the central Flux v2 repo. For convenience of
tracking membership and applying permissions, we can share maintainers
by:

 - referring to the MAINTAINERS file here, from elsewhere;
 - making a GitHub team that tracks those shared maintainers.

This commit makes a note of this in the MAINTAINERS file, to inform
people of the arrangement, and remind them to keep the team up to
date.

Signed-off-by: Michael Bridgen <michael@weave.works>
2020-11-27 12:44:17 +00:00
Stefan Prodan
b981bae1db Merge pull request #519 from fluxcd/service-account
Add service account arg to create commands
2020-11-27 12:28:25 +02:00
Stefan Prodan
d2df9ccf33 Add service account arg to create commands
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-27 12:20:55 +02:00
Stefan Prodan
5e51f51449 Merge pull request #518 from fluxcd/docs-upgrade
Add upgrade docs to install guide
2020-11-27 12:20:07 +02:00
Stefan Prodan
2c044a27e4 Add upgrade docs to install guide
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-27 08:44:34 +02:00
Hidde Beydals
d274a1115e Merge pull request #516 from fluxcd/update-components
Update source-controller to v0.4.1
2020-11-26 19:27:32 +01:00
fluxcdbot
bfae2899f3 Update toolkit components 2020-11-26 18:16:36 +00:00
Stefan Prodan
5352a7e13a Merge pull request #513 from fluxcd/update-components
Update toolkit components to v0.4.0
2020-11-26 18:45:38 +02:00
fluxcdbot
c49f9ef26a Update toolkit components 2020-11-26 16:32:23 +00:00
Stefan Prodan
4a7376c5f5 Merge pull request #512 from fluxcd/suspend-sources
Implement suspend/resume commands for sources
2020-11-26 17:25:47 +02:00
Stefan Prodan
567ce7f987 Add suspend status to get sources commands
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-26 17:17:50 +02:00
Stefan Prodan
26bc0a8100 Add source suspend/resume commands to docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-26 16:45:25 +02:00
Stefan Prodan
e7ff319685 Add resume source commands
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-26 16:40:54 +02:00
Stefan Prodan
072138deff Add suspend source commands
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-26 16:22:47 +02:00
Stefan Prodan
dd8dc90c1e Update source-controller to v0.4.0
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-26 16:20:00 +02:00
Stefan Prodan
8f1da33375 Merge pull request #511 from fluxcd/kubectl
Set kubecontext and kubeconfig for kubectl exec
2020-11-26 12:04:46 +02:00
Stefan Prodan
c02fbc2794 Set kubecontext and kubeconfig for kubectl exec
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-26 11:53:08 +02:00
Stefan Prodan
371db07108 Merge pull request #506 from chanwit/sops-doc
Add a note not to apply SOPS encrypted secrets directly
2020-11-25 17:49:49 +02:00
Chanwit Kaewkasi
99f5dbf16b add a note not to apply encrypted secrets directly
Signed-off-by: Chanwit Kaewkasi <chanwit@gmail.com>
2020-11-25 22:26:52 +07:00
Stefan Prodan
0db06c8962 Merge pull request #505 from fluxcd/tenant-sa
Add service accounts to tenant command
2020-11-25 17:22:49 +02:00
Stefan Prodan
a8e5876b2e Add create tenant to CLI docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-25 16:42:52 +02:00
Stefan Prodan
8273851b73 Add service accounts to tenant command
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-25 16:39:00 +02:00
Stefan Prodan
c2967240bb Merge pull request #501 from mewzherder/patch-6
Update Upcoming events w/ Nov 30 talk
2020-11-25 16:38:35 +02:00
mewzherder
282a6270c8 Update Upcoming events w/ Nov 30 talk
Signed-off-by: mewzherder <tamao@weave.works>
2020-11-24 09:03:10 -08:00
Stefan Prodan
1b299fad90 Merge pull request #498 from fluxcd/gh-action
Add GitHub Action for Flux CLI
2020-11-23 19:15:34 +02:00
Stefan Prodan
aa8dced7ad Add GitHub Action for Flux CLI
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-23 17:58:06 +02:00
Stefan Prodan
050ba951b0 Merge pull request #496 from gtseres/main
Add reference to the bootstrap migration
2020-11-23 15:49:53 +02:00
George Tseres
5e47c16099 Update docs/guides/flux-v1-migration.md
Co-authored-by: Stefan Prodan <stefan.prodan@gmail.com>
Signed-off-by: gtseres <george.tseres@gmail.com>
2020-11-23 15:39:39 +02:00
George Tseres
902db4c732 Add reference to the bootstrap migration
Signed-off-by: gtseres <george.tseres@gmail.com>
2020-11-23 15:28:42 +02:00
Stefan Prodan
86462fbee6 Merge pull request #491 from fluxcd/hr-values-from
Add values-from arg to create helmrelease cmd
2020-11-23 11:57:34 +02:00
Stefan Prodan
48bed79439 Add values-from arg to create helmrelease cmd
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-23 11:29:35 +02:00
Stefan Prodan
26b61c2b6b Merge pull request #492 from fluxcd/hr-kustomize-generators
Add docs on how to use Kustomize to generate Helm values
2020-11-23 10:18:54 +02:00
Stefan Prodan
3b2253ddc0 Add docs on how to use Kustomize to generate Helm values
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-22 11:30:35 +02:00
Stefan Prodan
5ddcb39129 Merge pull request #487 from fluxcd/sops-aws
Add AWS IAM role example to SOPS docs
2020-11-20 15:34:59 +02:00
Stefan Prodan
59adef5bcc Add AWS IAM role example to SOPS docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-20 15:26:10 +02:00
Stefan Prodan
875aefc8dd Merge pull request #486 from relu/add-relu-to-maintainers
Add @relu to maintainers list
2020-11-20 15:20:35 +02:00
Aurel Canciu
0dbc9d213e Add @relu to maintainers list
Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-11-20 15:03:15 +02:00
Hidde Beydals
9f4c53e321 Merge pull request #484 from fluxcd/source-reconcile-at
Utilize LastHandledReconcileAt for source reconcile commands
2020-11-20 13:35:27 +01:00
Hidde Beydals
3c8716f6ac Utilize LastHandledReconcileAt for reconcile cmds
Signed-off-by: Hidde Beydals <hello@hidde.co>
2020-11-20 13:28:28 +01:00
Hidde Beydals
1a7f31ae2e Merge pull request #485 from fluxcd/update-components
Update toolkit component manifests to v0.3.0
2020-11-20 13:28:18 +01:00
fluxcdbot
64ad69acfe Update toolkit components 2020-11-20 12:21:02 +00:00
Stefan Prodan
9f47b55aa9 Merge pull request #474 from relu/adopt-k8s-conditions
Update components to v0.3.0
2020-11-20 13:46:11 +02:00
Aurel Canciu
53a1db0703 Adjustments to support new sa name in kustomize
Supporting changes in fluxcd/kustomize-controller#180

Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-11-20 13:36:36 +02:00
Aurel Canciu
2a789ec705 Refactor to adopt k8s standardized Condition type
Updates to use metav1.Condition type and removes references for
deprecated corev1.Condition* constants and uses the new k8s api/meta
helpers in place of the old pkg/apis/meta types.

Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
2020-11-20 13:19:40 +02:00
Hidde Beydals
3047b25193 Merge pull request #476 from fluxcd/update-components
Update helm-controller to v0.2.2
2020-11-18 12:36:12 +01:00
fluxcdbot
f66399cdc0 Update toolkit components 2020-11-18 11:14:54 +00:00
Stefan Prodan
37fb0f632b Merge pull request #473 from fluxcd/update-components
Update helm-controller to v0.2.1
2020-11-17 13:59:54 +02:00
fluxcdbot
e5dd0c7ff8 Update toolkit components 2020-11-17 11:48:54 +00:00
Stefan Prodan
51392cd54c Merge pull request #468 from RossyWhite/fix-dryrun-option
Fix --dry-run option
2020-11-16 17:31:42 +02:00
RossyWhite
02bcb4ff3c Fix --dry-run option
Signed-off-by: RossyWhite <daikishiroi@gmail.com>
2020-11-16 18:03:08 +09:00
Stefan Prodan
d84297a5b5 Merge pull request #463 from fluxcd/update-components
Update source-controller to v0.2.2
2020-11-12 18:24:28 +02:00
fluxcdbot
c3876e30a9 Update toolkit components 2020-11-12 16:11:58 +00:00
Stefan Prodan
10711ed780 Merge pull request #462 from fluxcd/update-components
Update kustomize-controller to v0.2.2
2020-11-12 17:29:43 +02:00
fluxcdbot
de4e266e33 Update toolkit components 2020-11-12 15:17:44 +00:00
Stefan Prodan
15442969f8 Merge pull request #461 from fluxcd/fix-install-path
Fix cluster path in install docs
2020-11-12 16:30:08 +02:00
Stefan Prodan
bed48ada82 Fix cluster path in install docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-12 16:14:05 +02:00
Stefan Prodan
a66004f567 Merge pull request #458 from fluxcd/kind-update
Update engineerd/setup-kind to v0.5.0
2020-11-12 12:16:07 +02:00
Stefan Prodan
72a4e3b3b8 Update engineerd/setup-kind to v0.5.0
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-12 12:03:25 +02:00
Stefan Prodan
16761e4fca Merge pull request #455 from fluxcd/fix-gh-https
Fix GitHub bootstrap with token auth
2020-11-12 12:01:58 +02:00
Stefan Prodan
ba34a6d401 Fix GitHub bootstrap with token auth
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2020-11-12 09:47:04 +02:00
287 changed files with 10986 additions and 2656 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.

17
.github/aur/flux-bin/.SRCINFO.template vendored Normal file
View File

@@ -0,0 +1,17 @@
pkgbase = flux-bin
pkgdesc = Open and extensible continuous delivery solution for Kubernetes
pkgver = ${PKGVER}
pkgrel = ${PKGREL}
url = https://fluxcd.io/
arch = x86_64
arch = armv6h
arch = armv7h
arch = aarch64
license = APACHE
optdepends = kubectl
source_x86_64 = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_amd64.tar.gz
source_armv6h = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm.tar.gz
source_armv7h = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm.tar.gz
source_aarch64 = flux-bin-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v1/flux_${PKGVER}_linux_arm64.tar.gz
pkgname = flux-bin

1
.github/aur/flux-bin/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.pkg

39
.github/aur/flux-bin/PKGBUILD.template vendored Normal file
View File

@@ -0,0 +1,39 @@
# Maintainer: Aurel Canciu <aurelcanciu@gmail.com>
# Maintainer: Hidde Beydals <hello@hidde.co>
pkgname=flux-bin
pkgver=${PKGVER}
pkgrel=${PKGREL}
pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
url="https://fluxcd.io/"
arch=("x86_64" "armv6h" "armv7h" "aarch64")
license=("APACHE")
optdepends=("kubectl")
source_x86_64=(
"$pkgname-$pkgver.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_amd64.tar.gz"
)
source_armv6h=(
"$pkgname-$pkgver.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm.tar.gz"
)
source_armv7h=(
"$pkgname-$pkgver.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm.tar.gz"
)
source_aarch64=(
"$pkgname-$pkgver.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm64.tar.gz"
)
sha256sums_x86_64=(
${SHA256SUM_AMD64}
)
sha256sums_armv6h=(
${SHA256SUM_ARM}
)
sha256sums_armv7h=(
${SHA256SUM_ARM}
)
sha256sums_aarch64=(
${SHA256SUM_ARM64}
)
package() {
install -Dm755 flux "$pkgdir/usr/bin/flux"
}

55
.github/aur/flux-bin/publish.sh vendored Executable file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -e
WD=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)
PKGNAME=$(basename $WD)
ROOT=${WD%/.github/aur/$PKGNAME}
LOCKFILE=/tmp/aur-$PKGNAME.lock
exec 100>$LOCKFILE || exit 0
flock -n 100 || exit 0
trap "rm -f $LOCKFILE" EXIT
export VERSION=$1
echo "Publishing to AUR as version ${VERSION}"
cd $WD
export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
eval $(ssh-agent -s)
ssh-add <(echo "$AUR_BOT_SSH_PRIVATE_KEY")
GITDIR=$(mktemp -d /tmp/aur-$PKGNAME-XXX)
trap "rm -rf $GITDIR" EXIT
git clone aur@aur.archlinux.org:$PKGNAME $GITDIR 2>&1
CURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }')
CURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }')
export PKGVER=${VERSION/-/}
if [[ "${CURRENT_PKGVER}" == "${PKGVER}" ]]; then
export PKGREL=$((CURRENT_PKGREL+1))
else
export PKGREL=1
fi
export SHA256SUM_ARM=$(sha256sum ${ROOT}/dist/flux_${PKGVER}_linux_arm.tar.gz | awk '{ print $1 }')
export SHA256SUM_ARM64=$(sha256sum ${ROOT}/dist/flux_${PKGVER}_linux_arm64.tar.gz | awk '{ print $1 }')
export SHA256SUM_AMD64=$(sha256sum ${ROOT}/dist/flux_${PKGVER}_linux_amd64.tar.gz | awk '{ print $1 }')
envsubst '$PKGVER $PKGREL $SHA256SUM_AMD64 $SHA256SUM_ARM $SHA256SUM_ARM64' < .SRCINFO.template > $GITDIR/.SRCINFO
envsubst '$PKGVER $PKGREL $SHA256SUM_AMD64 $SHA256SUM_ARM $SHA256SUM_ARM64' < PKGBUILD.template > $GITDIR/PKGBUILD
cd $GITDIR
git config user.name "fluxcdbot"
git config user.email "fluxcdbot@users.noreply.github.com"
git add -A
if [ -z "$(git status --porcelain)" ]; then
echo "No changes."
else
git commit -m "Updated to version v${PKGVER} release ${PKGREL}"
git push origin master
fi

19
.github/aur/flux-go/.SRCINFO.template vendored Normal file
View File

@@ -0,0 +1,19 @@
pkgbase = flux-go
pkgdesc = Open and extensible continuous delivery solution for Kubernetes
pkgver = ${PKGVER}
pkgrel = ${PKGREL}
url = https://fluxcd.io/
arch = x86_64
arch = armv6h
arch = armv7h
arch = aarch64
license = APACHE
makedepends = go
depends = glibc
optdepends = kubectl
provides = flux-bin
conflicts = flux-bin
replaces = flux-cli
source = flux-go-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/archive/v${PKGVER}.tar.gz
pkgname = flux-go

1
.github/aur/flux-go/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.pkg

43
.github/aur/flux-go/PKGBUILD.template vendored Normal file
View File

@@ -0,0 +1,43 @@
# Maintainer: Aurel Canciu <aurelcanciu@gmail.com>
# Maintainer: Hidde Beydals <hello@hidde.co>
pkgname=flux-go
pkgver=${PKGVER}
pkgrel=${PKGREL}
pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
url="https://fluxcd.io/"
arch=("x86_64" "armv6h" "armv7h" "aarch64")
license=("APACHE")
provides=("flux-bin")
conflicts=("flux-bin")
replaces=("flux-cli")
depends=("glibc")
makedepends=("go")
optdepends=("kubectl")
source=(
"$pkgname-$pkgver.tar.gz::https://github.com/fluxcd/flux2/archive/v$pkgver.tar.gz"
)
sha256sums=(
${SHA256SUM}
)
build() {
cd "flux2-$pkgver"
export CGO_LDFLAGS="$LDFLAGS"
export CGO_CFLAGS="$CFLAGS"
export CGO_CXXFLAGS="$CXXFLAGS"
export CGO_CPPFLAGS="$CPPFLAGS"
export GOFLAGS="-buildmode=pie -trimpath -ldflags=-linkmode=external -mod=readonly -modcacherw"
go build -ldflags "-X main.VERSION=$pkgver" -o flux-bin ./cmd/flux
}
check() {
cd "flux2-$pkgver"
make test
}
package() {
cd "flux2-$pkgver"
install -Dm755 flux-bin "$pkgdir/usr/bin/flux"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}

53
.github/aur/flux-go/publish.sh vendored Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env bash
set -e
WD=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)
PKGNAME=$(basename $WD)
ROOT=${WD%/.github/aur/$PKGNAME}
LOCKFILE=/tmp/aur-$PKGNAME.lock
exec 100>$LOCKFILE || exit 0
flock -n 100 || exit 0
trap "rm -f $LOCKFILE" EXIT
export VERSION=$1
echo "Publishing to AUR as version ${VERSION}"
cd $WD
export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
eval $(ssh-agent -s)
ssh-add <(echo "$AUR_BOT_SSH_PRIVATE_KEY")
GITDIR=$(mktemp -d /tmp/aur-$PKGNAME-XXX)
trap "rm -rf $GITDIR" EXIT
git clone aur@aur.archlinux.org:$PKGNAME $GITDIR 2>&1
CURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }')
CURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }')
export PKGVER=${VERSION/-/}
if [[ "${CURRENT_PKGVER}" == "${PKGVER}" ]]; then
export PKGREL=$((CURRENT_PKGREL+1))
else
export PKGREL=1
fi
export SHA256SUM=$(curl -sL https://github.com/fluxcd/flux2/archive/v$PKGVER.tar.gz | sha256sum | awk '{ print $1 }')
envsubst '$PKGVER $PKGREL $SHA256SUM' < .SRCINFO.template > $GITDIR/.SRCINFO
envsubst '$PKGVER $PKGREL $SHA256SUM' < PKGBUILD.template > $GITDIR/PKGBUILD
cd $GITDIR
git config user.name "fluxcdbot"
git config user.email "fluxcdbot@users.noreply.github.com"
git add -A
if [ -z "$(git status --porcelain)" ]; then
echo "No changes."
else
git commit -m "Updated to version v${PKGVER} release ${PKGREL}"
git push origin master
fi

19
.github/aur/flux-scm/.SRCINFO.template vendored Normal file
View File

@@ -0,0 +1,19 @@
pkgbase = flux-scm
pkgdesc = Open and extensible continuous delivery solution for Kubernetes
pkgver = ${PKGVER}
pkgrel = ${PKGREL}
url = https://fluxcd.io/
arch = x86_64
arch = armv6h
arch = armv7h
arch = aarch64
license = APACHE
makedepends = go
depends = glibc
optdepends = kubectl
provides = flux-bin
conflicts = flux-bin
source = git+https://github.com/fluxcd/flux2.git
md5sums = SKIP
pkgname = flux-scm

1
.github/aur/flux-scm/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.pkg

45
.github/aur/flux-scm/PKGBUILD.template vendored Normal file
View File

@@ -0,0 +1,45 @@
# Maintainer: Aurel Canciu <aurelcanciu@gmail.com>
# Maintainer: Hidde Beydals <hello@hidde.co>
pkgname=flux-scm
pkgver=${PKGVER}
pkgrel=${PKGREL}
pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
url="https://fluxcd.io/"
arch=("x86_64" "armv6h" "armv7h" "aarch64")
license=("APACHE")
provides=("flux-bin")
conflicts=("flux-bin")
depends=("glibc")
makedepends=("go")
optdepends=("kubectl")
source=(
"git+https://github.com/fluxcd/flux2.git"
)
md5sums=('SKIP')
pkgver() {
cd "flux2"
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
build() {
cd "flux2"
export CGO_LDFLAGS="$LDFLAGS"
export CGO_CFLAGS="$CFLAGS"
export CGO_CXXFLAGS="$CXXFLAGS"
export CGO_CPPFLAGS="$CPPFLAGS"
export GOFLAGS="-buildmode=pie -trimpath -ldflags=-linkmode=external -mod=readonly -modcacherw"
go build -ldflags "-X main.VERSION=$pkgver" -o flux-bin ./cmd/flux
}
check() {
cd "flux2"
make test
}
package() {
cd "flux2"
install -Dm755 flux-bin "$pkgdir/usr/bin/flux"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}

51
.github/aur/flux-scm/publish.sh vendored Executable file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -e
WD=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)
PKGNAME=$(basename $WD)
ROOT=${WD%/.github/aur/$PKGNAME}
LOCKFILE=/tmp/aur-$PKGNAME.lock
exec 100>$LOCKFILE || exit 0
flock -n 100 || exit 0
trap "rm -f $LOCKFILE" EXIT
export VERSION=$1
echo "Publishing to AUR as version ${VERSION}"
cd $WD
export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
eval $(ssh-agent -s)
ssh-add <(echo "$AUR_BOT_SSH_PRIVATE_KEY")
GITDIR=$(mktemp -d /tmp/aur-$PKGNAME-XXX)
trap "rm -rf $GITDIR" EXIT
git clone aur@aur.archlinux.org:$PKGNAME $GITDIR 2>&1
CURRENT_PKGVER=$(cat $GITDIR/.SRCINFO | grep pkgver | awk '{ print $3 }')
CURRENT_PKGREL=$(cat $GITDIR/.SRCINFO | grep pkgrel | awk '{ print $3 }')
export PKGVER=${VERSION/-/}
if [[ "${CURRENT_PKGVER}" == "${PKGVER}" ]]; then
export PKGREL=$((CURRENT_PKGREL+1))
else
export PKGREL=1
fi
envsubst '$PKGVER $PKGREL' < .SRCINFO.template > $GITDIR/.SRCINFO
envsubst '$PKGVER $PKGREL' < PKGBUILD.template > $GITDIR/PKGBUILD
cd $GITDIR
git config user.name "fluxcdbot"
git config user.email "fluxcdbot@users.noreply.github.com"
git add -A
if [ -z "$(git status --porcelain)" ]; then
echo "No changes."
else
git commit -m "Updated to version v${PKGVER} release ${PKGREL}"
git push origin master
fi

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

@@ -21,9 +21,9 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.14.x go-version: 1.15.x
- name: Setup Kubernetes - name: Setup Kubernetes
uses: engineerd/setup-kind@v0.4.0 uses: engineerd/setup-kind@v0.5.0
- name: Set outputs - name: Set outputs
id: vars id: vars
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
@@ -49,8 +49,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: uninstall - name: uninstall
run: | run: |
./bin/flux suspend kustomization flux-system ./bin/flux uninstall --resources --crds -s --timeout=10m
./bin/flux uninstall --resources --crds -s
- name: bootstrap reinstall - name: bootstrap reinstall
run: | run: |
./bin/flux bootstrap github --manifests ./manifests/install/ \ ./bin/flux bootstrap github --manifests ./manifests/install/ \

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

@@ -24,9 +24,15 @@ jobs:
with: with:
go-version: 1.15.x go-version: 1.15.x
- name: Setup Kubernetes - name: Setup Kubernetes
uses: engineerd/setup-kind@v0.4.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 \
@@ -136,12 +151,45 @@ jobs:
- name: flux delete source git - name: flux delete source git
run: | run: |
./bin/flux delete source git podinfo --silent ./bin/flux delete source git podinfo --silent
- name: flux create tenant
run: |
./bin/flux create tenant dev-team --with-namespace=apps
./bin/flux -n apps create source helm podinfo \
--url https://stefanprodan.github.io/podinfo
./bin/flux -n apps create hr podinfo-helm \
--source=HelmRepository/podinfo \
--chart=podinfo \
--chart-version="5.0.x" \
--service-account=dev-team
- name: flux create image repository
run: |
./bin/flux create image repository podinfo \
--image=ghcr.io/stefanprodan/podinfo \
--interval=1m
- name: flux create image policy
run: |
./bin/flux create image policy podinfo \
--image-ref=podinfo \
--interval=1m \
--semver=5.0.x
- name: flux get image policy
run: |
./bin/flux get image policy podinfo | grep '5.0.3'
- name: flux2-kustomize-helm-example
run: |
./bin/flux create source git flux-system \
--url=https://github.com/fluxcd/flux2-kustomize-helm-example \
--branch=main
./bin/flux create kustomization flux-system \
--source=flux-system \
--path=./clusters/staging
kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=2m
- name: flux check - name: flux check
run: | run: |
./bin/flux check ./bin/flux check
- name: flux uninstall - name: flux uninstall
run: | run: |
./bin/flux uninstall --crds --silent ./bin/flux uninstall --crds --silent --timeout=10m
- name: Debug failure - name: Debug failure
if: failure() if: failure()
run: | run: |

View File

@@ -59,24 +59,9 @@ jobs:
# create tarball # create tarball
cd ./output && tar -cvzf manifests.tar.gz $files cd ./output && tar -cvzf manifests.tar.gz $files
- name: Create release - name: Generate install manifest
id: create_release run: |
uses: actions/create-release@latest kustomize build ./manifests/install > ./output/install.yaml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
- name: Upload artifacts
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./output/manifests.tar.gz
asset_name: manifests.tar.gz
asset_content_type: application/gzip
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v1 uses: goreleaser/goreleaser-action@v1
with: with:
@@ -85,3 +70,4 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
AUR_BOT_SSH_PRIVATE_KEY: ${{ secrets.AUR_BOT_SSH_PRIVATE_KEY }}

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:
@@ -30,7 +32,8 @@ jobs:
go mod edit -require="github.com/fluxcd/$1/api@${RELEASE_VERSION}" go mod edit -require="github.com/fluxcd/$1/api@${RELEASE_VERSION}"
fi fi
PR_BODY="$PR_BODY- $1 to ${RELEASE_VERSION}%0A" # NB: special URL encoded formatting required for newlines
PR_BODY="$PR_BODY- $1 to ${RELEASE_VERSION}%0A https://github.com/fluxcd/$1/blob/${RELEASE_VERSION}/CHANGELOG.md%0A"
fi fi
} }
@@ -40,6 +43,8 @@ jobs:
bump_version kustomize-controller bump_version kustomize-controller
bump_version source-controller bump_version source-controller
bump_version notification-controller bump_version notification-controller
bump_version image-reflector-controller
bump_version image-automation-controller
# add missing and remove unused modules # add missing and remove unused modules
go mod tidy go mod tidy
@@ -47,7 +52,7 @@ jobs:
# diff change # diff change
git diff git diff
# export PR_BODY for PR # export PR_BODY for PR and commit
echo "::set-output name=pr_body::$PR_BODY" echo "::set-output name=pr_body::$PR_BODY"
} }
@@ -56,19 +61,22 @@ jobs:
uses: peter-evans/create-pull-request@v3 uses: peter-evans/create-pull-request@v3
with: with:
token: ${{ secrets.BOT_GITHUB_TOKEN }} token: ${{ secrets.BOT_GITHUB_TOKEN }}
commit-message: Update toolkit components commit-message: |
Update toolkit components
${{ steps.update.outputs.pr_body }}
committer: GitHub <noreply@github.com> committer: GitHub <noreply@github.com>
author: fluxcdbot <fluxcdbot@users.noreply.github.com> author: fluxcdbot <fluxcdbot@users.noreply.github.com>
signoff: true
branch: update-components
title: Update toolkit components title: Update toolkit components
body: | body: |
${{ steps.update.outputs.pr_body }} ${{ steps.update.outputs.pr_body }}
labels: |
Auto-generated by [create-pull-request][1] area/build
[1]: https://github.com/peter-evans/create-pull-request
branch: update-components
reviewers: ${{ secrets.ASSIGNEES }} reviewers: ${{ secrets.ASSIGNEES }}
- name: Check output - name: Check output
run: | run: |
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"

View File

@@ -1,3 +1,4 @@
project_name: flux
builds: builds:
- <<: &build_defaults - <<: &build_defaults
binary: flux binary: flux
@@ -50,3 +51,23 @@ brews:
type: optional type: optional
test: | test: |
system "#{bin}/flux --version" system "#{bin}/flux --version"
publishers:
- name: aur-pkg-bin
env:
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
cmd: |
.github/aur/flux-bin/publish.sh {{ .Version }}
- name: aur-pkg-scm
env:
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
cmd: |
.github/aur/flux-scm/publish.sh {{ .Version }}
- name: aur-pkg-go
env:
- AUR_BOT_SSH_PRIVATE_KEY={{ .Env.AUR_BOT_SSH_PRIVATE_KEY }}
cmd: |
.github/aur/flux-go/publish.sh {{ .Version }}
release:
extra_files:
- glob: ./output/manifests.tar.gz
- glob: ./output/install.yaml

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

@@ -2,7 +2,17 @@ The maintainers are generally available in Slack at
https://cloud-native.slack.com in #flux (https://cloud-native.slack.com/messages/CLAJ40HV3) https://cloud-native.slack.com in #flux (https://cloud-native.slack.com/messages/CLAJ40HV3)
(obtain an invitation at https://slack.cncf.io/). (obtain an invitation at https://slack.cncf.io/).
These maintainers are shared with other Flux v2-related git
repositories under https://github.com/fluxcd, as noted in their
respective MAINTAINERS files.
For convenience, they are reflected in the GitHub team
@fluxcd/flux2-maintainers -- if the list here changes, that team also
should.
In alphabetical order: In alphabetical order:
Aurel Canciu, Sortlist <aurel@sortlist.com> (github: @relu, slack: relu)
Hidde Beydals, Weaveworks <hidde@weave.works> (github: @hiddeco, slack: hidde) Hidde Beydals, Weaveworks <hidde@weave.works> (github: @hiddeco, slack: hidde)
Philip Laine, Xenit <philip.laine@xenit.se> (github: @phillebaba, slack: phillebaba)
Stefan Prodan, Weaveworks <stefan@weave.works> (github: @stefanprodan, slack: stefanprodan) Stefan Prodan, Weaveworks <stefan@weave.works> (github: @stefanprodan, slack: stefanprodan)

View File

@@ -36,6 +36,15 @@ curl -s https://toolkit.fluxcd.io/install.sh | sudo bash
. <(flux completion bash) . <(flux completion bash)
``` ```
Arch Linux (AUR) packages:
- [flux-bin](https://aur.archlinux.org/packages/flux-bin): install the latest
stable version using a pre-build binary (recommended)
- [flux-go](https://aur.archlinux.org/packages/flux-go): build the latest
stable version from source code
- [flux-scm](https://aur.archlinux.org/packages/flux-scm): build the latest
(unstable) version from source code from our git `main` branch
Binaries for macOS, Windows and Linux AMD64/ARM are available to download on the Binaries for macOS, Windows and Linux AMD64/ARM are available to download on the
[release page](https://github.com/fluxcd/flux2/releases). [release page](https://github.com/fluxcd/flux2/releases).
@@ -57,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
@@ -88,29 +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
### Featured Talks ### Events
- 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/) Check out our **[events calendar](https://fluxcd.io/community/#talks)**, both with upcoming talks you can attend or past events videos you can watch.
- 19 Oct 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 1 with Leigh Capili](https://youtu.be/0v5bjysXTL8)
- 12 Oct 2020 - [Rawkode Live: Introduction to GitOps Toolkit with Stefan Prodan](https://youtu.be/HqTzuOBP0eY)
- 4 Sep 2020 - [KubeCon Europe: The road to Flux v2 and Progressive Delivery with Stefan Prodan & Hidde Beydals](https://youtu.be/8v94nUkXsxU)
- 25 June 2020 - [Cloud Native Nordics: Introduction to GitOps & GitOps Toolkit with Alexis Richardson & Stefan Prodan](https://youtu.be/qQBtSkgl7tI)
- 7 May 2020 - [GitOps Days - Community Special: GitOps Toolkit Experimentation with Stefan Prodan](https://youtu.be/WHzxunv4DKk?t=6521)
### Upcoming Events We look forward to seeing you with us!
- 2 Nov 2020 - [The Power of GitOps with Flux & GitOps Toolkit - Part 2 with Leigh Capili](https://www.meetup.com/GitOps-Community/events/273934676/)
- 12-13 Nov 2020 - [GitOps Days EMEA](https://www.gitopsdays.com/) with talks and workshops on migrating to Flux v2 and Helm Controller
- 19 Nov 2020 - [KubeCon NA: Progressive Delivery Techniques with Flagger and Flux v2 with Stefan Prodan](https://kccncna20.sched.com/event/1b04f8408b49976b843a5d0019cb8112)
We are looking forward to seeing you with us!

6
action/Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM stefanprodan/alpine-base:latest
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

89
action/README.md Normal file
View File

@@ -0,0 +1,89 @@
# Flux GitHub Action
Usage:
```yaml
steps:
- name: Setup Flux CLI
uses: fluxcd/flux2/action@main
- name: Run Flux commands
run: flux -v
```
This action places the `flux` binary inside your repository root under `bin/flux`.
You should add `bin/flux` to your `.gitignore` file, as in the following example:
```gitignore
# ignore flux binary
bin/flux
```
Note that this action can only be used on GitHub **Linux AMD64** runners.
### Automate Flux updates
Example workflow for updating Flux's components generated with `flux bootstrap --path=clusters/production`:
```yaml
name: update-flux
on:
workflow_dispatch:
schedule:
- cron: "0 * * * *"
jobs:
components:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Setup Flux CLI
uses: fluxcd/flux2/action@main
- name: Check for updates
id: update
run: |
flux install \
--export > ./clusters/production/flux-system/gotk-components.yaml
VERSION="$(flux -v)"
echo "::set-output name=flux_version::$VERSION"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: update-flux
commit-message: Update to ${{ steps.update.outputs.flux_version }}
title: Update to ${{ steps.update.outputs.flux_version }}
body: |
${{ steps.update.outputs.flux_version }}
```
### End-to-end testing
Example workflow for running Flux in Kubernetes Kind:
```yaml
name: e2e
on:
push:
branches:
- '*'
jobs:
kubernetes:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Flux CLI
uses: fluxcd/flux2/action@main
- name: Setup Kubernetes Kind
uses: engineerd/setup-kind@v0.5.0
- name: Install Flux in Kubernetes Kind
run: flux install
```
A complete e2e testing workflow is available here
[flux2-kustomize-helm-example](https://github.com/fluxcd/flux2-kustomize-helm-example/blob/main/.github/workflows/e2e.yaml)

15
action/action.yml Normal file
View File

@@ -0,0 +1,15 @@
name: 'kustomize'
description: 'A GitHub Action for running Flux commands'
author: 'Flux project'
branding:
icon: 'command'
color: 'blue'
inputs:
version:
description: 'strict semver'
required: false
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.version }}

40
action/entrypoint.sh Executable file
View File

@@ -0,0 +1,40 @@
#!/bin/bash
# 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.
set -e
VERSION=$1
if [ -z $VERSION ]; then
# Find latest release if no version is specified
VERSION=$(curl https://api.github.com/repos/fluxcd/flux2/releases/latest -sL | grep tag_name | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-)
fi
# Download linux binary
BIN_URL="https://github.com/fluxcd/flux2/releases/download/v${VERSION}/flux_${VERSION}_linux_amd64.tar.gz"
curl -sL $BIN_URL | tar xz
# Copy binary to GitHub runner
mkdir -p $GITHUB_WORKSPACE/bin
mv ./flux $GITHUB_WORKSPACE/bin
chmod +x $GITHUB_WORKSPACE/bin/flux
# Print version
$GITHUB_WORKSPACE/bin/flux -v
# Add binary to GitHub runner path
echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH
echo "$RUNNER_WORKSPACE/$(basename $GITHUB_REPOSITORY)/bin" >> $GITHUB_PATH

View File

@@ -45,79 +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
bootstrapComponents []string defaultComponents []string
bootstrapRegistry string extraComponents []string
bootstrapImagePullSecret string registry string
bootstrapBranch string imagePullSecret string
bootstrapWatchAllNamespaces bool branch string
bootstrapNetworkPolicy bool watchAllNamespaces bool
bootstrapManifestsPath string networkPolicy bool
bootstrapArch = flags.Arch(defaults.Arch) manifestsPath string
bootstrapLogLevel = flags.LogLevel(defaults.LogLevel) arch flags.Arch
bootstrapRequiredComponents = []string{"source-controller", "kustomize-controller"} logLevel flags.LogLevel
bootstrapTokenAuth bool requiredComponents []string
) tokenAuth bool
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(&bootstrapComponents, "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().StringVar(&bootstrapRegistry, "registry", "ghcr.io/fluxcd", bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil,
"list of components in addition to those supplied or defaulted, accepts comma-separated values")
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(&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 {
return append(bootstrapArgs.defaultComponents, bootstrapArgs.extraComponents...)
}
func bootstrapValidate() error { func bootstrapValidate() error {
for _, component := range bootstrapRequiredComponents { components := bootstrapComponents()
if !utils.ContainsItemString(bootstrapComponents, component) { for _, component := range bootstrapArgs.requiredComponents {
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: 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: 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)
@@ -125,30 +149,33 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes
return "", fmt.Errorf("generating install manifests failed: %w", err) return "", fmt.Errorf("generating install manifests failed: %w", err)
} }
if filePath, err := output.WriteFile(tmpDir); err != nil { filePath, err := output.WriteFile(tmpDir)
if err != nil {
return "", fmt.Errorf("generating install manifests failed: %w", err) return "", fmt.Errorf("generating install manifests failed: %w", err)
} else {
return filePath, nil
} }
return filePath, nil
} }
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, 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 { statusChecker, err := NewStatusChecker(time.Second, time.Minute)
kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()} if err != nil {
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubectlArgs...); err != nil { return fmt.Errorf("install failed: %w", err)
return fmt.Errorf("install failed")
}
} }
logger.Waitingf("verifying installation")
if err := statusChecker.Assess(components...); err != nil {
return fmt.Errorf("install failed")
}
return nil return nil
} }
func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir string, interval time.Duration) error { func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir string, interval time.Duration) (string, error) {
opts := sync.Options{ opts := sync.Options{
Name: name, Name: name,
Namespace: namespace, Namespace: namespace,
@@ -161,36 +188,36 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
manifest, err := sync.Generate(opts) manifest, err := sync.Generate(opts)
if err != nil { if err != nil {
return fmt.Errorf("generating install manifests failed: %w", err) return "", fmt.Errorf("generating install manifests failed: %w", err)
} }
if _, err := manifest.WriteFile(tmpDir); err != nil { output, err := manifest.WriteFile(tmpDir)
return err if err != nil {
return "", err
} }
outputDir := filepath.Dir(output)
if err := utils.GenerateKustomizationYaml(filepath.Join(tmpDir, targetPath, namespace)); err != nil { if err := utils.GenerateKustomizationYaml(outputDir); err != nil {
return err return "", err
} }
return outputDir, nil
return nil
} }
func applySyncManifests(ctx context.Context, kubeClient client.Client, name, namespace, targetPath, tmpDir string) error { func applySyncManifests(ctx context.Context, kubeClient client.Client, name, namespace, manifestsPath string) error {
kubectlArgs := []string{"apply", "-k", filepath.Join(tmpDir, targetPath, namespace)} kubectlArgs := []string{"apply", "-k", manifestsPath}
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, 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
} }
@@ -225,7 +252,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
} }
@@ -252,3 +279,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,14 +23,17 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/git"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
) )
var bootstrapGitHubCmd = &cobra.Command{ var bootstrapGitHubCmd = &cobra.Command{
@@ -54,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>
@@ -68,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 string 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().StringVar(&ghPath, "path", "", "repository path, 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)
@@ -112,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
} }
@@ -144,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
@@ -155,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
@@ -167,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")
manifest, err := generateInstallManifests(ghPath, 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, 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
} }
@@ -195,29 +211,26 @@ 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, manifest, bootstrapComponents); err != nil { if err := applyInstallManifests(ctx, installManifest, bootstrapComponents()); err != nil {
return err return err
} }
logger.Successf("install completed") logger.Successf("install completed")
} }
if bootstrapTokenAuth { repoURL := repository.GetURL()
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",
@@ -229,21 +242,22 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
} }
} else { } else {
// setup SSH deploy key // setup SSH deploy key
if shouldCreateDeployKey(ctx, kubeClient, namespace) { repoURL = repository.GetSSH()
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 {
@@ -256,12 +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")
if err := generateSyncManifests(repository.GetSSH(), bootstrapBranch, namespace, namespace, ghPath, tmpDir, ghInterval); err != nil { syncManifests, err := generateSyncManifests(repoURL, bootstrapArgs.branch, rootArgs.namespace, rootArgs.namespace, filepath.ToSlash(githubArgs.path.String()), tmpDir, githubArgs.interval)
if err != nil {
return err return err
} }
// commit and push manifests // commit and push manifests
if changed, err = repository.Commit(ctx, path.Join(ghPath, 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 {
@@ -272,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, ghPath, tmpDir); err != nil { if err := applySyncManifests(ctx, kubeClient, rootArgs.namespace, rootArgs.namespace, syncManifests); err != nil {
return err return err
} }

View File

@@ -23,14 +23,18 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath"
"regexp"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/git"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
) )
var bootstrapGitLabCmd = &cobra.Command{ var bootstrapGitLabCmd = &cobra.Command{
@@ -44,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
@@ -56,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
@@ -65,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 string
) )
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().StringVar(&glPath, "path", "", "repository path, 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)
} }
@@ -95,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
@@ -138,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")
manifest, err := generateInstallManifests(glPath, 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, 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
} }
@@ -167,12 +191,12 @@ 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, manifest, bootstrapComponents); err != nil { if err := applyInstallManifests(ctx, installManifest, bootstrapComponents()); err != nil {
return err return err
} }
logger.Successf("install completed") logger.Successf("install completed")
@@ -180,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",
@@ -198,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 {
@@ -225,12 +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")
if err := generateSyncManifests(repoURL, bootstrapBranch, namespace, namespace, glPath, tmpDir, glInterval); err != nil { syncManifests, err := generateSyncManifests(repoURL, bootstrapArgs.branch, rootArgs.namespace, rootArgs.namespace, filepath.ToSlash(gitlabArgs.path.String()), tmpDir, gitlabArgs.interval)
if err != nil {
return err return err
} }
// commit and push manifests // commit and push manifests
if changed, err = repository.Commit(ctx, path.Join(glPath, 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 {
@@ -241,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, glPath, tmpDir); err != nil { if err := applySyncManifests(ctx, kubeClient, rootArgs.namespace, rootArgs.namespace, syncManifests); err != nil {
return err return err
} }

View File

@@ -22,13 +22,13 @@ import (
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"time"
"github.com/blang/semver/v4" "github.com/blang/semver/v4"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
apimachineryversion "k8s.io/apimachinery/pkg/version" apimachineryversion "k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
) )
var checkCmd = &cobra.Command{ var checkCmd = &cobra.Command{
@@ -45,25 +45,30 @@ 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
) extraComponents []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")
checkCmd.Flags().StringSliceVar(&checkArgs.extraComponents, "components-extra", nil,
"list of components in addition to those supplied or defaulted, 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")
@@ -77,7 +82,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)
} }
@@ -104,7 +109,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, 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
@@ -133,7 +138,7 @@ func kubectlCheck(ctx context.Context, version string) bool {
} }
func kubernetesCheck(version string) bool { func kubernetesCheck(version string) bool {
cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig) 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
@@ -168,20 +173,25 @@ 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()
statusChecker, err := NewStatusChecker(time.Second, 30*time.Second)
if err != nil {
return false
}
ok := true ok := true
for _, deployment := range checkComponents { deployments := append(checkArgs.components, checkArgs.extraComponents...)
kubectlArgs := []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()} for _, deployment := range deployments {
if output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, kubectlArgs...); err != nil { if err := statusChecker.Assess(deployment); err != nil {
logger.Failuref("%s: %s", deployment, strings.TrimSuffix(output, "\n"))
ok = false ok = false
} else { } else {
logger.Successf("%s is healthy", deployment) logger.Successf("%s: healthy", deployment)
} }
kubectlArgs = []string{"-n", namespace, "get", "deployment", deployment, "-o", "jsonpath=\"{..image}\""}
if output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, kubectlArgs...); err == nil { kubectlArgs := []string{"-n", rootArgs.namespace, "get", "deployment", deployment, "-o", "jsonpath=\"{..image}\""}
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

@@ -37,7 +37,7 @@ command -v flux >/dev/null && . <(flux completion zsh) && compdef _flux flux
or write a cached file in one of the completion directories in your ${fpath}: or write a cached file in one of the completion directories in your ${fpath}:
echo "${fpath// /\n}" | grep -i completion echo "${fpath// /\n}" | grep -i completion
flux completions zsh > _flux flux completion zsh > _flux
mv _flux ~/.oh-my-zsh/completions # oh-my-zsh mv _flux ~/.oh-my-zsh/completions # oh-my-zsh
mv _flux ~/.zprezto/modules/completion/external/src/ # zprezto mv _flux ~/.zprezto/modules/completion/external/src/ # zprezto

View File

@@ -17,13 +17,19 @@ limitations under the License.
package main package main
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"k8s.io/apimachinery/pkg/util/validation"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"github.com/fluxcd/flux2/internal/utils"
) )
var createCmd = &cobra.Command{ var createCmd = &cobra.Command{
@@ -32,23 +38,97 @@ 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)
} }
// upsertable is an interface for values that can be used in `upsert`.
type upsertable interface {
adapter
named
}
// upsert updates or inserts an object. Instead of providing the
// object itself, you provide a named (as in Name and Namespace)
// template value, and a mutate function which sets the values you
// want to update. The mutate function is nullary -- you mutate a
// value in the closure, e.g., by doing this:
//
// var existing Value
// existing.Name = name
// existing.Namespace = ns
// upsert(ctx, client, valueAdapter{&value}, func() error {
// value.Spec = onePreparedEarlier
// })
func (names apiType) upsert(ctx context.Context, kubeClient client.Client, object upsertable, mutate func() error) (types.NamespacedName, error) {
nsname := types.NamespacedName{
Namespace: object.GetNamespace(),
Name: object.GetName(),
}
op, err := controllerutil.CreateOrUpdate(ctx, kubeClient, object.asClientObject(), mutate)
if err != nil {
return nsname, err
}
switch op {
case controllerutil.OperationResultCreated:
logger.Successf("%s created", names.kind)
case controllerutil.OperationResultUpdated:
logger.Successf("%s updated", names.kind)
}
return nsname, nil
}
type upsertWaitable interface {
upsertable
statusable
}
// upsertAndWait encodes the pattern of creating or updating a
// resource, then waiting for it to reconcile. See the note on
// `upsert` for how to work with the `mutate` argument.
func (names apiType) upsertAndWait(object upsertWaitable, mutate func() error) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) // NB globals
if err != nil {
return err
}
logger.Generatef("generating %s", names.kind)
logger.Actionf("applying %s", names.kind)
namespacedName, err := imageRepositoryType.upsert(ctx, kubeClient, object, mutate)
if err != nil {
return err
}
logger.Waitingf("waiting for %s reconciliation", names.kind)
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isReady(ctx, kubeClient, namespacedName, object)); err != nil {
return err
}
logger.Successf("%s reconciliation completed", names.kind)
return nil
}
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,18 +20,18 @@ 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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"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"
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{
@@ -48,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)
} }
@@ -67,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)
@@ -93,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
} }
@@ -132,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
} }
@@ -179,11 +181,11 @@ func isAlertReady(ctx context.Context, kubeClient client.Client,
return false, err return false, err
} }
if c := meta.GetCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil { if c := apimeta.FindStatusCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status { switch c.Status {
case corev1.ConditionTrue: case metav1.ConditionTrue:
return true, nil return true, nil
case corev1.ConditionFalse: case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message) return false, fmt.Errorf(c.Message)
} }
} }

View File

@@ -21,16 +21,17 @@ 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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"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{
@@ -53,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)
} }
@@ -76,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")
} }
@@ -85,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
} }
@@ -128,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
} }
@@ -177,11 +180,11 @@ func isAlertProviderReady(ctx context.Context, kubeClient client.Client,
return false, err return false, err
} }
if c := meta.GetCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil { if c := apimeta.FindStatusCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status { switch c.Status {
case corev1.ConditionTrue: case metav1.ConditionTrue:
return true, nil return true, nil
case corev1.ConditionFalse: case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message) return false, fmt.Errorf(c.Message)
} }
} }

View File

@@ -18,6 +18,7 @@ package main
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@@ -28,6 +29,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
@@ -61,11 +63,20 @@ var createHelmReleaseCmd = &cobra.Command{
--source=Bucket/podinfo \ --source=Bucket/podinfo \
--chart=./charts/podinfo --chart=./charts/podinfo
# Create a HelmRelease with values from a local YAML file # Create a HelmRelease with values from local YAML files
flux create hr podinfo \ flux create hr podinfo \
--source=HelmRepository/podinfo \ --source=HelmRepository/podinfo \
--chart=podinfo \ --chart=podinfo \
--values=./my-values.yaml --values=./my-values1.yaml \
--values=./my-values2.yaml
# Create a HelmRelease with values from a Kubernetes secret
kubectl -n app create secret generic my-secret-values \
--from-file=values.yaml=/path/to/my-secret-values.yaml
flux -n app create hr podinfo \
--source=HelmRepository/podinfo \
--chart=podinfo \
--values-from=Secret/my-secret-values
# Create a HelmRelease with a custom release name # Create a HelmRelease with a custom release name
flux create hr podinfo \ flux create hr podinfo \
@@ -89,24 +100,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
) valuesFrom flags.HelmReleaseValuesFrom
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(&hrValuesFile, "values", "", "local path to the values.yaml file") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.saName, "service-account", "", "the name of the service account to impersonate when reconciling this HelmRelease")
createHelmReleaseCmd.Flags().StringArrayVar(&helmReleaseArgs.valuesFile, "values", nil, "local path to values.yaml files")
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.valuesFrom, "values-from", helmReleaseArgs.valuesFrom.Description())
createCmd.AddCommand(createHelmReleaseCmd) createCmd.AddCommand(createHelmReleaseCmd)
} }
@@ -116,7 +133,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")
} }
@@ -125,30 +142,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,
}, },
}, },
}, },
@@ -156,28 +173,58 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}, },
} }
if hrValuesFile != "" { if helmReleaseArgs.saName != "" {
data, err := ioutil.ReadFile(hrValuesFile) helmRelease.Spec.ServiceAccountName = helmReleaseArgs.saName
if err != nil {
return fmt.Errorf("reading values from %s failed: %w", hrValuesFile, err)
}
json, err := yaml.YAMLToJSON(data)
if err != nil {
return fmt.Errorf("converting values to JSON from %s failed: %w", hrValuesFile, err)
}
helmRelease.Spec.Values = &apiextensionsv1.JSON{Raw: json}
} }
if export { if len(helmReleaseArgs.valuesFile) > 0 {
var valuesMap map[string]interface{}
for _, v := range helmReleaseArgs.valuesFile {
data, err := ioutil.ReadFile(v)
if err != nil {
return fmt.Errorf("reading values from %s failed: %w", v, err)
}
jsonBytes, err := yaml.YAMLToJSON(data)
if err != nil {
return fmt.Errorf("converting values to JSON from %s failed: %w", v, err)
}
jsonMap := make(map[string]interface{})
if err := json.Unmarshal(jsonBytes, &jsonMap); err != nil {
return fmt.Errorf("unmarshaling values from %s failed: %w", v, err)
}
if valuesMap == nil {
valuesMap = jsonMap
} else {
valuesMap = utils.MergeMaps(valuesMap, jsonMap)
}
}
jsonRaw, err := json.Marshal(valuesMap)
if err != nil {
return fmt.Errorf("marshaling values failed: %w", err)
}
helmRelease.Spec.Values = &apiextensionsv1.JSON{Raw: jsonRaw}
}
if helmReleaseArgs.valuesFrom.String() != "" {
helmRelease.Spec.ValuesFrom = []helmv2.ValuesReference{{
Kind: helmReleaseArgs.valuesFrom.Kind,
Name: helmReleaseArgs.valuesFrom.Name,
}}
}
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
} }
@@ -189,7 +236,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
} }
@@ -243,6 +290,6 @@ func isHelmReleaseReady(ctx context.Context, kubeClient client.Client,
return false, nil return false, nil
} }
return meta.HasReadyCondition(helmRelease.Status.Conditions), nil return apimeta.IsStatusConditionTrue(helmRelease.Status.Conditions, meta.ReadyCondition), nil
} }
} }

38
cmd/flux/create_image.go Normal file
View File

@@ -0,0 +1,38 @@
/*
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 (
"strings"
"github.com/spf13/cobra"
)
const createImageLong = `
The create image sub-commands work with image automation objects; that is,
object controlling updates to git based on e.g., new container images
being available.`
var createImageCmd = &cobra.Command{
Use: "image",
Short: "Create or update resources dealing with image automation",
Long: strings.TrimSpace(createImageLong),
}
func init() {
createCmd.AddCommand(createImageCmd)
}

View File

@@ -0,0 +1,119 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/apis/meta"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var createImagePolicyCmd = &cobra.Command{
Use: "policy <name>",
Short: "Create or update an ImagePolicy object",
Long: `The create image policy command generates an ImagePolicy resource.
An ImagePolicy object calculates a "latest image" given an image
repository and a policy, e.g., semver.
The image that sorts highest according to the policy is recorded in
the status of the object.`,
RunE: createImagePolicyRun}
type imagePolicyFlags struct {
imageRef string
semver string
filterRegex string
}
var imagePolicyArgs = imagePolicyFlags{}
func init() {
flags := createImagePolicyCmd.Flags()
flags.StringVar(&imagePolicyArgs.imageRef, "image-ref", "", "the name of an image repository object")
flags.StringVar(&imagePolicyArgs.semver, "semver", "", "a semver range to apply to tags; e.g., '1.x'")
flags.StringVar(&imagePolicyArgs.filterRegex, "filter-regex", "", " regular expression pattern used to filter the image tags")
createImageCmd.AddCommand(createImagePolicyCmd)
}
// getObservedGeneration is implemented here, since it's not
// (presently) needed elsewhere.
func (obj imagePolicyAdapter) getObservedGeneration() int64 {
return obj.ImagePolicy.Status.ObservedGeneration
}
func createImagePolicyRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("ImagePolicy name is required")
}
objectName := args[0]
if imagePolicyArgs.imageRef == "" {
return fmt.Errorf("the name of an ImageRepository in the namespace is required (--image-ref)")
}
labels, err := parseLabels()
if err != nil {
return err
}
var policy = imagev1.ImagePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: objectName,
Namespace: rootArgs.namespace,
Labels: labels,
},
Spec: imagev1.ImagePolicySpec{
ImageRepositoryRef: meta.LocalObjectReference{
Name: imagePolicyArgs.imageRef,
},
},
}
switch {
case imagePolicyArgs.semver != "":
policy.Spec.Policy.SemVer = &imagev1.SemVerPolicy{
Range: imagePolicyArgs.semver,
}
default:
return fmt.Errorf("a policy must be provided with --semver")
}
if imagePolicyArgs.filterRegex != "" {
policy.Spec.FilterTags = &imagev1.TagFilter{
Pattern: imagePolicyArgs.filterRegex,
}
}
if createArgs.export {
return printExport(exportImagePolicy(&policy))
}
var existing imagev1.ImagePolicy
copyName(&existing, &policy)
err = imagePolicyType.upsertAndWait(imagePolicyAdapter{&existing}, func() error {
existing.Spec = policy.Spec
existing.SetLabels(policy.Labels)
return nil
})
return err
}

View File

@@ -0,0 +1,143 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"time"
"github.com/google/go-containerregistry/pkg/name"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/apis/meta"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var createImageRepositoryCmd = &cobra.Command{
Use: "repository <name>",
Short: "Create or update an ImageRepository object",
Long: `The create image repository command generates an ImageRepository resource.
An ImageRepository object specifies an image repository to scan.`,
Example: ` # Create an ImageRepository object to scan the alpine image repository:
flux create image repository alpine-repo --image alpine --interval 20m
# Create an image repository that uses an image pull secret (assumed to
# have been created already):
flux create image repository myapp-repo \
--secret-ref image-pull \
--image ghcr.io/example.com/myapp --interval 5m
# Create a TLS secret for a local image registry using a self-signed
# host certificate, and use it to scan an image. ca.pem is a file
# containing the CA certificate used to sign the host certificate.
flux create secret tls local-registry-cert --ca-file ./ca.pem
flux create image repository app-repo \
--cert-secret-ref local-registry-cert \
--image local-registry:5000/app --interval 5m
# Create a TLS secret with a client certificate and key, and use it
# to scan a private image registry.
flux create secret tls client-cert \
--cert-file client.crt --key-file client.key
flux create image repository app-repo \
--cert-secret-ref client-cert \
--image registry.example.com/private/app --interval 5m
`,
RunE: createImageRepositoryRun,
}
type imageRepoFlags struct {
image string
secretRef string
certSecretRef string
timeout time.Duration
}
var imageRepoArgs = imageRepoFlags{}
func init() {
flags := createImageRepositoryCmd.Flags()
flags.StringVar(&imageRepoArgs.image, "image", "", "the image repository to scan; e.g., library/alpine")
flags.StringVar(&imageRepoArgs.secretRef, "secret-ref", "", "the name of a docker-registry secret to use for credentials")
flags.StringVar(&imageRepoArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates")
// NB there is already a --timeout in the global flags, for
// controlling timeout on operations while e.g., creating objects.
flags.DurationVar(&imageRepoArgs.timeout, "scan-timeout", 0, "a timeout for scanning; this defaults to the interval if not set")
createImageCmd.AddCommand(createImageRepositoryCmd)
}
func createImageRepositoryRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("ImageRepository name is required")
}
objectName := args[0]
if imageRepoArgs.image == "" {
return fmt.Errorf("an image repository (--image) is required")
}
if _, err := name.NewRepository(imageRepoArgs.image); err != nil {
return fmt.Errorf("unable to parse image value: %w", err)
}
labels, err := parseLabels()
if err != nil {
return err
}
var repo = imagev1.ImageRepository{
ObjectMeta: metav1.ObjectMeta{
Name: objectName,
Namespace: rootArgs.namespace,
Labels: labels,
},
Spec: imagev1.ImageRepositorySpec{
Image: imageRepoArgs.image,
Interval: metav1.Duration{Duration: createArgs.interval},
},
}
if imageRepoArgs.timeout != 0 {
repo.Spec.Timeout = &metav1.Duration{Duration: imageRepoArgs.timeout}
}
if imageRepoArgs.secretRef != "" {
repo.Spec.SecretRef = &meta.LocalObjectReference{
Name: imageRepoArgs.secretRef,
}
}
if imageRepoArgs.certSecretRef != "" {
repo.Spec.CertSecretRef = &meta.LocalObjectReference{
Name: imageRepoArgs.certSecretRef,
}
}
if createArgs.export {
return printExport(exportImageRepository(&repo))
}
// a temp value for use with the rest
var existing imagev1.ImageRepository
copyName(&existing, &repo)
err = imageRepositoryType.upsertAndWait(imageRepositoryAdapter{&existing}, func() error {
existing.Spec = repo.Spec
existing.Labels = repo.Labels
return nil
})
return err
}

View File

@@ -0,0 +1,115 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/apis/meta"
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
)
var createImageUpdateCmd = &cobra.Command{
Use: "update <name>",
Short: "Create or update an ImageUpdateAutomation object",
Long: `The create image update command generates an ImageUpdateAutomation resource.
An ImageUpdateAutomation object specifies an automated update to images
mentioned in YAMLs in a git repository.`,
RunE: createImageUpdateRun,
}
type imageUpdateFlags struct {
// git checkout spec
gitRepoRef string
branch string
// commit spec
commitTemplate string
authorName string
authorEmail string
}
var imageUpdateArgs = imageUpdateFlags{}
func init() {
flags := createImageUpdateCmd.Flags()
flags.StringVar(&imageUpdateArgs.gitRepoRef, "git-repo-ref", "", "the name of a GitRepository resource with details of the upstream git repository")
flags.StringVar(&imageUpdateArgs.branch, "branch", "", "the branch to checkout and push commits to")
flags.StringVar(&imageUpdateArgs.commitTemplate, "commit-template", "", "a template for commit messages")
flags.StringVar(&imageUpdateArgs.authorName, "author-name", "", "the name to use for commit author")
flags.StringVar(&imageUpdateArgs.authorEmail, "author-email", "", "the email to use for commit author")
createImageCmd.AddCommand(createImageUpdateCmd)
}
func createImageUpdateRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("ImageUpdateAutomation name is required")
}
objectName := args[0]
if imageUpdateArgs.gitRepoRef == "" {
return fmt.Errorf("a reference to a GitRepository is required (--git-repo-ref)")
}
if imageUpdateArgs.branch == "" {
return fmt.Errorf("the Git repository branch is required (--branch)")
}
labels, err := parseLabels()
if err != nil {
return err
}
var update = autov1.ImageUpdateAutomation{
ObjectMeta: metav1.ObjectMeta{
Name: objectName,
Namespace: rootArgs.namespace,
Labels: labels,
},
Spec: autov1.ImageUpdateAutomationSpec{
Checkout: autov1.GitCheckoutSpec{
GitRepositoryRef: meta.LocalObjectReference{
Name: imageUpdateArgs.gitRepoRef,
},
Branch: imageUpdateArgs.branch,
},
Interval: metav1.Duration{Duration: createArgs.interval},
Commit: autov1.CommitSpec{
AuthorName: imageUpdateArgs.authorName,
AuthorEmail: imageUpdateArgs.authorEmail,
MessageTemplate: imageUpdateArgs.commitTemplate,
},
},
}
if createArgs.export {
return printExport(exportImageUpdate(&update))
}
var existing autov1.ImageUpdateAutomation
copyName(&existing, &update)
err = imageUpdateAutomationType.upsertAndWait(imageUpdateAutomationAdapter{&existing}, func() error {
existing.Spec = update.Spec
existing.Labels = update.Labels
return nil
})
return err
}

View File

@@ -23,18 +23,19 @@ 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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"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{
@@ -71,55 +72,61 @@ var createKsCmd = &cobra.Command{
RunE: createKsCmdRun, RunE: createKsCmdRun,
} }
var ( type kustomizationFlags struct {
ksSource flags.KustomizationSource source flags.KustomizationSource
ksPath string 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
ksSANamespace string decryptionProvider flags.DecryptionProvider
ksDecryptionProvider flags.DecryptionProvider decryptionSecret string
ksDecryptionSecret string targetNamespace string
ksTargetNamespace 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().StringVar(&ksPath, "path", "./", "path to the directory containing the Kustomization 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, "sa-name", "", "service account name") createKsCmd.Flags().StringVar(&kustomizationArgs.saName, "service-account", "", "the name of the service account to impersonate when reconciling this Kustomization")
createKsCmd.Flags().StringVar(&ksSANamespace, "sa-namespace", "", "service account namespace") createKsCmd.Flags().Var(&kustomizationArgs.decryptionProvider, "decryption-provider", kustomizationArgs.decryptionProvider.Description())
createKsCmd.Flags().Var(&ksDecryptionProvider, "decryption-provider", ksDecryptionProvider.Description()) createKsCmd.Flags().StringVar(&kustomizationArgs.decryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption")
createKsCmd.Flags().StringVar(&ksDecryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption") createKsCmd.Flags().StringVar(&kustomizationArgs.targetNamespace, "target-namespace", "", "overrides the namespace of all Kustomization objects reconciled by this Kustomization")
createKsCmd.Flags().StringVar(&ksTargetNamespace, "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, "./") { 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
} }
@@ -127,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, 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)
@@ -171,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],
@@ -184,35 +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 != "" && ksSANamespace != "" { if kustomizationArgs.saName != "" {
kustomization.Spec.ServiceAccount = &kustomizev1.ServiceAccount{ kustomization.Spec.ServiceAccountName = kustomizationArgs.saName
Name: ksSAName,
Namespace: ksSANamespace,
}
} }
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
} }
@@ -224,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
} }
@@ -278,11 +282,11 @@ func isKustomizationReady(ctx context.Context, kubeClient client.Client,
return false, nil return false, nil
} }
if c := meta.GetCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil { if c := apimeta.FindStatusCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status { switch c.Status {
case corev1.ConditionTrue: case metav1.ConditionTrue:
return true, nil return true, nil
case corev1.ConditionFalse: case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message) return false, fmt.Errorf(c.Message)
} }
} }

View File

@@ -21,16 +21,17 @@ 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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"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{
@@ -49,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)
} }
@@ -70,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)
@@ -100,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
} }
@@ -140,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
} }
@@ -189,11 +192,11 @@ func isReceiverReady(ctx context.Context, kubeClient client.Client,
return false, err return false, err
} }
if c := meta.GetCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil { if c := apimeta.FindStatusCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status { switch c.Status {
case corev1.ConditionTrue: case metav1.ConditionTrue:
return true, nil return true, nil
case corev1.ConditionFalse: case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message) return false, fmt.Errorf(c.Message)
} }
} }

99
cmd/flux/create_secret.go Normal file
View File

@@ -0,0 +1,99 @@
/*
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 (
"context"
"fmt"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
)
var createSecretCmd = &cobra.Command{
Use: "secret",
Short: "Create or update Kubernetes secrets",
Long: "The create source sub-commands generate Kubernetes secrets specific to Flux.",
}
func init() {
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 {
secret.TypeMeta = metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
}
data, err := yaml.Marshal(secret)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}

View File

@@ -0,0 +1,205 @@
/*
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 (
"context"
"crypto/elliptic"
"fmt"
"net/url"
"time"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/ssh"
)
var createSecretGitCmd = &cobra.Command{
Use: "git [name]",
Short: "Create or update a Kubernetes secret for Git authentication",
Long: `
The create secret git command generates a Kubernetes secret with Git credentials.
For Git over SSH, the host and SSH keys are automatically generated and stored in the secret.
For Git over HTTP/S, the provided basic authentication credentials are stored in the secret.`,
Example: ` # Create a Git SSH authentication secret using an ECDSA P-521 curve public key
flux create secret git podinfo-auth \
--url=ssh://git@github.com/stefanprodan/podinfo \
--ssh-key-algorithm=ecdsa \
--ssh-ecdsa-curve=p521
# Create a secret for a Git repository using basic authentication
flux create secret git podinfo-auth \
--url=https://github.com/stefanprodan/podinfo \
--username=username \
--password=password
# Create a Git SSH secret on disk and print the deploy key
flux create secret git podinfo-auth \
--url=ssh://git@github.com/stefanprodan/podinfo \
--export > podinfo-auth.yaml
yq read podinfo-auth.yaml 'data."identity.pub"' | base64 --decode
# Create a Git SSH secret on disk and encrypt it with Mozilla SOPS
flux create secret git podinfo-auth \
--namespace=apps \
--url=ssh://git@github.com/stefanprodan/podinfo \
--export > podinfo-auth.yaml
sops --encrypt --encrypted-regex '^(data|stringData)$' \
--in-place podinfo-auth.yaml
`,
RunE: createSecretGitCmdRun,
}
type secretGitFlags struct {
url string
username string
password string
keyAlgorithm flags.PublicKeyAlgorithm
rsaBits flags.RSAKeyBits
ecdsaCurve flags.ECDSACurve
}
var secretGitArgs = NewSecretGitFlags()
func init() {
createSecretGitCmd.Flags().StringVar(&secretGitArgs.url, "url", "", "git address, e.g. ssh://git@host/org/repository")
createSecretGitCmd.Flags().StringVarP(&secretGitArgs.username, "username", "u", "", "basic authentication username")
createSecretGitCmd.Flags().StringVarP(&secretGitArgs.password, "password", "p", "", "basic authentication password")
createSecretGitCmd.Flags().Var(&secretGitArgs.keyAlgorithm, "ssh-key-algorithm", secretGitArgs.keyAlgorithm.Description())
createSecretGitCmd.Flags().Var(&secretGitArgs.rsaBits, "ssh-rsa-bits", secretGitArgs.rsaBits.Description())
createSecretGitCmd.Flags().Var(&secretGitArgs.ecdsaCurve, "ssh-ecdsa-curve", secretGitArgs.ecdsaCurve.Description())
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 {
if len(args) < 1 {
return fmt.Errorf("secret name is required")
}
name := args[0]
secret, err := makeSecret(name)
if err != nil {
return err
}
if secretGitArgs.url == "" {
return fmt.Errorf("url is required")
}
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 {
case "ssh":
pair, err := generateKeyPair(ctx, secretGitArgs.keyAlgorithm, secretGitArgs.rsaBits, secretGitArgs.ecdsaCurve)
if err != nil {
return err
}
hostKey, err := scanHostKey(ctx, u)
if err != nil {
return err
}
secret.StringData = map[string]string{
"identity": string(pair.PrivateKey),
"identity.pub": string(pair.PublicKey),
"known_hosts": string(hostKey),
}
if !createArgs.export {
logger.Generatef("deploy key: %s", string(pair.PublicKey))
}
case "http", "https":
if secretGitArgs.username == "" || secretGitArgs.password == "" {
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
secret.StringData = map[string]string{
"username": secretGitArgs.username,
"password": secretGitArgs.password,
}
default:
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
}
if createArgs.export {
return exportSecret(secret)
}
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
}
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,54 +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,
}, },
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 {
@@ -168,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")
@@ -182,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,25 +23,39 @@ 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"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"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",
@@ -86,42 +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
)
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().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")
} }
@@ -131,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,55 +155,59 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
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{},
}, },
} }
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.Actionf("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
} }
fmt.Printf("%s", pair.PublicKey) logger.Successf("deploy key: %s", pair.PublicKey)
prompt := promptui.Prompt{ prompt := promptui.Prompt{
Label: "Have you added the deploy key to your repository", Label: "Have you added the deploy key to your repository",
IsConfirm: true, IsConfirm: true,
@@ -206,14 +221,14 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
logger.Successf("collected public key from SSH server:") logger.Successf("collected public key from SSH server:\n%s", hostKey)
fmt.Printf("%s", hostKey)
logger.Actionf("applying secret with keys") logger.Actionf("applying secret with keys")
secret := corev1.Secret{ secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: rootArgs.namespace,
Labels: sourceLabels,
}, },
StringData: map[string]string{ StringData: map[string]string{
"identity": string(pair.PrivateKey), "identity": string(pair.PrivateKey),
@@ -225,16 +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,
}, },
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 {
@@ -251,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,
} }
} }
@@ -266,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
} }
@@ -279,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{
@@ -375,11 +334,11 @@ func isGitRepositoryReady(ctx context.Context, kubeClient client.Client,
return false, err return false, err
} }
if c := meta.GetCondition(gitRepository.Status.Conditions, meta.ReadyCondition); c != nil { if c := apimeta.FindStatusCondition(gitRepository.Status.Conditions, meta.ReadyCondition); c != nil {
switch c.Status { switch c.Status {
case corev1.ConditionTrue: case metav1.ConditionTrue:
return true, nil return true, nil
case corev1.ConditionFalse: case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message) return false, fmt.Errorf(c.Message)
} }
} }

View File

@@ -23,16 +23,19 @@ import (
"net/url" "net/url"
"os" "os"
"github.com/fluxcd/pkg/apis/meta"
"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" "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"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{
@@ -62,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)
} }
@@ -90,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")
} }
@@ -105,77 +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,
}, },
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)
} }
@@ -185,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")
@@ -199,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
} }
@@ -242,3 +248,28 @@ func upsertHelmRepository(ctx context.Context, kubeClient client.Client,
logger.Successf("source updated") logger.Successf("source updated")
return namespacedName, nil return namespacedName, nil
} }
func isHelmRepositoryReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, helmRepository *sourcev1.HelmRepository) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, helmRepository)
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if helmRepository.Generation != helmRepository.Status.ObservedGeneration {
return false, nil
}
if c := apimeta.FindStatusCondition(helmRepository.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

@@ -38,7 +38,7 @@ var createTenantCmd = &cobra.Command{
Use: "tenant", Use: "tenant",
Short: "Create or update a tenant", Short: "Create or update a tenant",
Long: ` Long: `
The create tenant command generates namespaces and role bindings to limit the The create tenant command generates namespaces, service accounts and role bindings to limit the
reconcilers scope to the tenant namespaces.`, reconcilers scope to the tenant namespaces.`,
Example: ` # Create a tenant with access to a namespace Example: ` # Create a tenant with access to a namespace
flux create tenant dev-team \ flux create tenant dev-team \
@@ -55,19 +55,19 @@ reconcilers scope to the tenant namespaces.`,
} }
const ( const (
tenantLabel = "toolkit.fluxcd.io/tenant" tenantLabel = "toolkit.fluxcd.io/tenant"
tenantRoleBinding = "gotk-reconciler"
) )
var ( type tenantFlags struct {
tenantNamespaces []string namespaces []string
tenantClusterRole string clusterRole string
) }
var tenantArgs tenantFlags
func init() { func init() {
createTenantCmd.Hidden = true createTenantCmd.Flags().StringSliceVar(&tenantArgs.namespaces, "with-namespace", nil, "namespace belonging to this tenant")
createTenantCmd.Flags().StringSliceVar(&tenantNamespaces, "with-namespace", nil, "namespace belonging to this tenant") createTenantCmd.Flags().StringVar(&tenantArgs.clusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding")
createTenantCmd.Flags().StringVar(&tenantClusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding")
createCmd.AddCommand(createTenantCmd) createCmd.AddCommand(createTenantCmd)
} }
@@ -80,18 +80,19 @@ 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")
} }
var namespaces []corev1.Namespace var namespaces []corev1.Namespace
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)
} }
@@ -111,9 +112,19 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
} }
namespaces = append(namespaces, namespace) namespaces = append(namespaces, namespace)
account := corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: tenant,
Namespace: ns,
Labels: objLabels,
},
}
accounts = append(accounts, account)
roleBinding := rbacv1.RoleBinding{ roleBinding := rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: tenantRoleBinding, Name: fmt.Sprintf("%s-reconciler", tenant),
Namespace: ns, Namespace: ns,
Labels: objLabels, Labels: objLabels,
}, },
@@ -123,39 +134,49 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
Kind: "User", Kind: "User",
Name: fmt.Sprintf("gotk:%s:reconciler", ns), Name: fmt.Sprintf("gotk:%s:reconciler", ns),
}, },
{
Kind: "ServiceAccount",
Name: tenant,
Namespace: ns,
},
}, },
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], roleBindings[i]); err != nil { if err := exportTenant(namespaces[i], accounts[i], roleBindings[i]); err != nil {
return err return err
} }
} }
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
} }
logger.Actionf("applying service account %s", accounts[i].Name)
if err := upsertServiceAccount(ctx, kubeClient, accounts[i]); err != nil {
return err
}
logger.Actionf("applying role binding %s", roleBindings[i].Name) logger.Actionf("applying role binding %s", roleBindings[i].Name)
if err := upsertRoleBinding(ctx, kubeClient, roleBindings[i]); err != nil { if err := upsertRoleBinding(ctx, kubeClient, roleBindings[i]); err != nil {
return err return err
@@ -195,6 +216,35 @@ func upsertNamespace(ctx context.Context, kubeClient client.Client, namespace co
return nil return nil
} }
func upsertServiceAccount(ctx context.Context, kubeClient client.Client, account corev1.ServiceAccount) error {
namespacedName := types.NamespacedName{
Namespace: account.GetNamespace(),
Name: account.GetName(),
}
var existing corev1.ServiceAccount
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, &account); err != nil {
return err
} else {
return nil
}
}
return err
}
if !equality.Semantic.DeepDerivative(account.Labels, existing.Labels) {
existing.Labels = account.Labels
if err := kubeClient.Update(ctx, &existing); err != nil {
return err
}
}
return nil
}
func upsertRoleBinding(ctx context.Context, kubeClient client.Client, roleBinding rbacv1.RoleBinding) error { func upsertRoleBinding(ctx context.Context, kubeClient client.Client, roleBinding rbacv1.RoleBinding) error {
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: roleBinding.GetNamespace(), Namespace: roleBinding.GetNamespace(),
@@ -228,7 +278,7 @@ func upsertRoleBinding(ctx context.Context, kubeClient client.Client, roleBindin
return nil return nil
} }
func exportTenant(namespace corev1.Namespace, roleBinding rbacv1.RoleBinding) error { func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, roleBinding rbacv1.RoleBinding) error {
namespace.TypeMeta = metav1.TypeMeta{ namespace.TypeMeta = metav1.TypeMeta{
APIVersion: "v1", APIVersion: "v1",
Kind: "Namespace", Kind: "Namespace",
@@ -242,6 +292,19 @@ func exportTenant(namespace corev1.Namespace, roleBinding rbacv1.RoleBinding) er
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1) data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
fmt.Println(resourceToString(data)) fmt.Println(resourceToString(data))
account.TypeMeta = metav1.TypeMeta{
APIVersion: "v1",
Kind: "ServiceAccount",
}
data, err = yaml.Marshal(account)
if err != nil {
return err
}
fmt.Println("---")
data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1)
fmt.Println(resourceToString(data))
roleBinding.TypeMeta = metav1.TypeMeta{ roleBinding.TypeMeta = metav1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1", APIVersion: "rbac.authorization.k8s.io/v1",
Kind: "RoleBinding", Kind: "RoleBinding",

View File

@@ -17,7 +17,14 @@ limitations under the License.
package main package main
import ( import (
"context"
"fmt"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"github.com/fluxcd/flux2/internal/utils"
) )
var deleteCmd = &cobra.Command{ var deleteCmd = &cobra.Command{
@@ -26,13 +33,64 @@ 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)
} }
type deleteCommand struct {
apiType
object adapter // for getting the value, and later deleting it
}
func (del deleteCommand) run(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("%s name is required", del.humanKind)
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, del.object.asClientObject())
if err != nil {
return err
}
if !deleteArgs.silent {
prompt := promptui.Prompt{
Label: "Are you sure you want to delete this " + del.humanKind,
IsConfirm: true,
}
if _, err := prompt.Run(); err != nil {
return fmt.Errorf("aborting")
}
}
logger.Actionf("deleting %s %s in %s namespace", del.humanKind, name, rootArgs.namespace)
err = kubeClient.Delete(ctx, del.object.asClientObject())
if err != nil {
return err
}
logger.Successf("%s deleted", del.humanKind)
return nil
}

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
}

31
cmd/flux/delete_image.go Normal file
View File

@@ -0,0 +1,31 @@
/*
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 (
"github.com/spf13/cobra"
)
var deleteImageCmd = &cobra.Command{
Use: "image",
Short: "Delete image automation objects",
Long: "The delete image sub-commands delete image automation objects.",
}
func init() {
deleteCmd.AddCommand(deleteImageCmd)
}

View File

@@ -0,0 +1,40 @@
/*
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 (
"github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var deleteImagePolicyCmd = &cobra.Command{
Use: "policy [name]",
Short: "Delete an ImagePolicy object",
Long: "The delete image policy command deletes the given ImagePolicy from the cluster.",
Example: ` # Delete an image policy
flux delete image policy alpine3.x
`,
RunE: deleteCommand{
apiType: imagePolicyType,
object: universalAdapter{&imagev1.ImagePolicy{}},
}.run,
}
func init() {
deleteImageCmd.AddCommand(deleteImagePolicyCmd)
}

View File

@@ -0,0 +1,40 @@
/*
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 (
"github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var deleteImageRepositoryCmd = &cobra.Command{
Use: "repository [name]",
Short: "Delete an ImageRepository object",
Long: "The delete image repository command deletes the given ImageRepository from the cluster.",
Example: ` # Delete an image repository
flux delete image repository alpine
`,
RunE: deleteCommand{
apiType: imageRepositoryType,
object: universalAdapter{&imagev1.ImageRepository{}},
}.run,
}
func init() {
deleteImageCmd.AddCommand(deleteImageRepositoryCmd)
}

View File

@@ -0,0 +1,40 @@
/*
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 (
"github.com/spf13/cobra"
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
)
var deleteImageUpdateCmd = &cobra.Command{
Use: "update [name]",
Short: "Delete an ImageUpdateAutomation object",
Long: "The delete image update command deletes the given ImageUpdateAutomation from the cluster.",
Example: ` # Delete an image update automation
flux delete image update latest-images
`,
RunE: deleteCommand{
apiType: imageUpdateAutomationType,
object: universalAdapter{&autov1.ImageUpdateAutomation{}},
}.run,
}
func init() {
deleteImageCmd.AddCommand(deleteImageUpdateCmd)
}

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

@@ -18,8 +18,15 @@ package main
import ( import (
"bytes" "bytes"
"context"
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/internal/utils"
) )
var exportCmd = &cobra.Command{ var exportCmd = &cobra.Command{
@@ -28,16 +35,91 @@ 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)
} }
// exportable represents a type that you can fetch from the Kubernetes
// API, then tidy up for serialising.
type exportable interface {
adapter
export() interface{}
}
// exportableList represents a type that has a list of values, each of
// which is exportable.
type exportableList interface {
listAdapter
exportItem(i int) interface{}
}
type exportCommand struct {
object exportable
list exportableList
}
func (export exportCommand) run(cmd *cobra.Command, args []string) error {
if !exportArgs.all && len(args) < 1 {
return fmt.Errorf("name is required")
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
if exportArgs.all {
err = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(rootArgs.namespace))
if err != nil {
return err
}
if export.list.len() == 0 {
logger.Failuref("no objects found in %s namespace", rootArgs.namespace)
return nil
}
for i := 0; i < export.list.len(); i++ {
if err = printExport(export.list.exportItem(i)); err != nil {
return err
}
}
} else {
name := args[0]
namespacedName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, export.object.asClientObject())
if err != nil {
return err
}
return printExport(export.object.export())
}
return nil
}
func printExport(export interface{}) error {
data, err := yaml.Marshal(export)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}
func resourceToString(data []byte) string { func resourceToString(data []byte) string {
data = bytes.Replace(data, []byte(" creationTimestamp: null\n"), []byte(""), 1) data = bytes.Replace(data, []byte(" creationTimestamp: null\n"), []byte(""), 1)
data = bytes.Replace(data, []byte("status: {}\n"), []byte(""), 1) data = bytes.Replace(data, []byte("status: {}\n"), []byte(""), 1)

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

31
cmd/flux/export_image.go Normal file
View File

@@ -0,0 +1,31 @@
/*
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 (
"github.com/spf13/cobra"
)
var exportImageCmd = &cobra.Command{
Use: "image",
Short: "Export image automation objects",
Long: "The export image sub-commands export image automation objects in YAML format.",
}
func init() {
exportCmd.AddCommand(exportImageCmd)
}

View File

@@ -0,0 +1,72 @@
/*
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 (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var exportImagePolicyCmd = &cobra.Command{
Use: "policy [name]",
Short: "Export ImagePolicy resources in YAML format",
Long: "The export image policy command exports one or all ImagePolicy resources in YAML format.",
Example: ` # Export all ImagePolicy resources
flux export image policy --all > image-policies.yaml
# Export a specific policy
flux export image policy alpine1x > alpine1x.yaml
`,
RunE: exportCommand{
object: imagePolicyAdapter{&imagev1.ImagePolicy{}},
list: imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
}.run,
}
func init() {
exportImageCmd.AddCommand(exportImagePolicyCmd)
}
// Export returns a ImagePolicy value which has extraneous information
// stripped out.
func exportImagePolicy(item *imagev1.ImagePolicy) interface{} {
gvk := imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)
export := imagev1.ImagePolicy{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: item.Name,
Namespace: item.Namespace,
Labels: item.Labels,
Annotations: item.Annotations,
},
Spec: item.Spec,
}
return export
}
func (ex imagePolicyAdapter) export() interface{} {
return exportImagePolicy(ex.ImagePolicy)
}
func (ex imagePolicyListAdapter) exportItem(i int) interface{} {
return exportImagePolicy(&ex.ImagePolicyList.Items[i])
}

View File

@@ -0,0 +1,70 @@
/*
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 (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var exportImageRepositoryCmd = &cobra.Command{
Use: "repository [name]",
Short: "Export ImageRepository resources in YAML format",
Long: "The export image repository command exports one or all ImageRepository resources in YAML format.",
Example: ` # Export all ImageRepository resources
flux export image repository --all > image-repositories.yaml
# Export a specific ImageRepository resource
flux export image repository alpine > alpine.yaml
`,
RunE: exportCommand{
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
}.run,
}
func init() {
exportImageCmd.AddCommand(exportImageRepositoryCmd)
}
func exportImageRepository(repo *imagev1.ImageRepository) interface{} {
gvk := imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)
export := imagev1.ImageRepository{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: repo.Name,
Namespace: repo.Namespace,
Labels: repo.Labels,
Annotations: repo.Annotations,
},
Spec: repo.Spec,
}
return export
}
func (ex imageRepositoryAdapter) export() interface{} {
return exportImageRepository(ex.ImageRepository)
}
func (ex imageRepositoryListAdapter) exportItem(i int) interface{} {
return exportImageRepository(&ex.ImageRepositoryList.Items[i])
}

View File

@@ -0,0 +1,72 @@
/*
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 (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
)
var exportImageUpdateCmd = &cobra.Command{
Use: "update [name]",
Short: "Export ImageUpdateAutomation resources in YAML format",
Long: "The export image update command exports one or all ImageUpdateAutomation resources in YAML format.",
Example: ` # Export all ImageUpdateAutomation resources
flux export image update --all > updates.yaml
# Export a specific automation
flux export image update latest-images > latest.yaml
`,
RunE: exportCommand{
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
list: imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
}.run,
}
func init() {
exportImageCmd.AddCommand(exportImageUpdateCmd)
}
// exportImageUpdate returns a value which has extraneous information
// stripped out.
func exportImageUpdate(item *autov1.ImageUpdateAutomation) interface{} {
gvk := autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)
export := autov1.ImageUpdateAutomation{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: item.Name,
Namespace: item.Namespace,
Labels: item.Labels,
Annotations: item.Annotations,
},
Spec: item.Spec,
}
return export
}
func (ex imageUpdateAutomationAdapter) export() interface{} {
return exportImageUpdate(ex.ImageUpdateAutomation)
}
func (ex imageUpdateAutomationListAdapter) exportItem(i int) interface{} {
return exportImageUpdate(&ex.ImageUpdateAutomationList.Items[i])
}

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

@@ -17,7 +17,17 @@ limitations under the License.
package main package main
import ( import (
"context"
"os"
"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"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/flux2/internal/utils"
) )
var getCmd = &cobra.Command{ var getCmd = &cobra.Command{
@@ -26,10 +36,81 @@ 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 {
listAdapter
summariseItem(i int, includeNamespace bool) []string
headers(includeNamespace bool) []string
}
// --- these help with implementations of summarisable
func statusAndMessage(conditions []metav1.Condition) (string, string) {
if c := apimeta.FindStatusCondition(conditions, meta.ReadyCondition); c != nil {
return string(c.Status), c.Message
}
return string(metav1.ConditionFalse), "waiting to be reconciled"
}
func nameColumns(item named, includeNamespace bool) []string {
if includeNamespace {
return []string{item.GetNamespace(), item.GetName()}
}
return []string{item.GetName()}
}
var namespaceHeader = []string{"Namespace"}
type getCommand struct {
apiType
list summarisable
}
func (get getCommand) run(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
var listOpts []client.ListOption
if !getArgs.allNamespaces {
listOpts = append(listOpts, client.InNamespace(rootArgs.namespace))
}
if len(args) > 0 {
listOpts = append(listOpts, client.MatchingFields{"metadata.name": args[0]})
}
err = kubeClient.List(ctx, get.list.asClientList(), listOpts...)
if err != nil {
return err
}
if get.list.len() == 0 {
logger.Failuref("no %s objects found in %s namespace", get.kind, rootArgs.namespace)
return nil
}
header := get.list.headers(getArgs.allNamespaces)
var rows [][]string
for i := 0; i < get.list.len(); i++ {
row := get.list.summariseItem(i, getArgs.allNamespaces)
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
}

View File

@@ -23,7 +23,8 @@ import (
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
@@ -32,9 +33,10 @@ import (
) )
var getAlertCmd = &cobra.Command{ var getAlertCmd = &cobra.Command{
Use: "alerts", Use: "alerts",
Short: "Get Alert statuses", Aliases: []string{"alert"},
Long: "The get alert command prints the statuses of the resources.", Short: "Get Alert statuses",
Long: "The get alert command prints the statuses of the resources.",
Example: ` # List all Alerts and their status Example: ` # List all Alerts and their status
flux get alerts flux get alerts
`, `,
@@ -46,17 +48,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...)
@@ -65,35 +67,33 @@ 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", "Suspended", "Ready", "Message"} 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
for _, alert := range list.Items { for _, alert := range list.Items {
row := []string{} row := []string{}
if c := meta.GetCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil { if c := apimeta.FindStatusCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{ row = []string{
alert.GetName(), alert.GetName(),
//alert.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(alert.Spec.Suspend)),
string(c.Status), string(c.Status),
c.Message, c.Message,
strings.Title(strconv.FormatBool(alert.Spec.Suspend)),
} }
} else { } else {
row = []string{ row = []string{
alert.GetName(), alert.GetName(),
//alert.Status.LastAppliedRevision, string(metav1.ConditionFalse),
strings.Title(strconv.FormatBool(alert.Spec.Suspend)),
string(corev1.ConditionFalse),
"waiting to be reconciled", "waiting to be reconciled",
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

@@ -21,7 +21,8 @@ import (
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
@@ -30,9 +31,10 @@ import (
) )
var getAlertProviderCmd = &cobra.Command{ var getAlertProviderCmd = &cobra.Command{
Use: "alert-providers", Use: "alert-providers",
Short: "Get Provider statuses", Aliases: []string{"alert-provider"},
Long: "The get alert-provider command prints the statuses of the resources.", Short: "Get Provider statuses",
Long: "The get alert-provider command prints the statuses of the resources.",
Example: ` # List all Providers and their status Example: ` # List all Providers and their status
flux get alert-providers flux get alert-providers
`, `,
@@ -44,17 +46,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...)
@@ -63,18 +65,18 @@ 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
for _, provider := range list.Items { for _, provider := range list.Items {
row := []string{} row := []string{}
if c := meta.GetCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil { if c := apimeta.FindStatusCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{ row = []string{
provider.GetName(), provider.GetName(),
string(c.Status), string(c.Status),
@@ -83,11 +85,11 @@ func getAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
} else { } else {
row = []string{ row = []string{
provider.GetName(), provider.GetName(),
string(corev1.ConditionFalse), string(metav1.ConditionFalse),
"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,89 +17,43 @@ 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"
corev1 "k8s.io/api/core/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{
Use: "helmreleases", Use: "helmreleases",
Aliases: []string{"hr"}, Aliases: []string{"hr", "helmrelease"},
Short: "Get HelmRelease statuses", Short: "Get HelmRelease statuses",
Long: "The get helmreleases command prints the statuses of the resources.", Long: "The get helmreleases command prints the statuses of the resources.",
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", "Revision", "Suspended", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, helmRelease := range list.Items {
row := []string{}
if c := meta.GetCondition(helmRelease.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
helmRelease.GetName(),
helmRelease.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(helmRelease.Spec.Suspend)),
string(c.Status),
c.Message,
}
} else {
row = []string{
helmRelease.GetName(),
helmRelease.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(helmRelease.Spec.Suspend)),
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{helmRelease.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
} }

32
cmd/flux/get_image.go Normal file
View File

@@ -0,0 +1,32 @@
/*
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 (
"github.com/spf13/cobra"
)
var getImageCmd = &cobra.Command{
Use: "images",
Aliases: []string{"image"},
Short: "Get image automation object status",
Long: "The get image sub-commands print the status of image automation objects.",
}
func init() {
getCmd.AddCommand(getImageCmd)
}

View File

@@ -0,0 +1,57 @@
/*
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 (
"github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var getImagePolicyCmd = &cobra.Command{
Use: "policy",
Short: "Get ImagePolicy status",
Long: "The get image policy command prints the status of ImagePolicy objects.",
Example: ` # List all image policies and their status
flux get image policy
# List image policies from all namespaces
flux get image policy --all-namespaces
`,
RunE: getCommand{
apiType: imagePolicyType,
list: &imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
}.run,
}
func init() {
getImageCmd.AddCommand(getImagePolicyCmd)
}
func (s imagePolicyListAdapter) summariseItem(i int, includeNamespace bool) []string {
item := s.Items[i]
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace), status, msg, item.Status.LatestImage)
}
func (s imagePolicyListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Ready", "Message", "Latest image"}
if includeNamespace {
return append(namespaceHeader, headers...)
}
return headers
}

View File

@@ -0,0 +1,66 @@
/*
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 (
"strconv"
"strings"
"time"
"github.com/spf13/cobra"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var getImageRepositoryCmd = &cobra.Command{
Use: "repository",
Short: "Get ImageRepository status",
Long: "The get image repository command prints the status of ImageRepository objects.",
Example: ` # List all image repositories and their status
flux get image repository
# List image repositories from all namespaces
flux get image repository --all-namespaces
`,
RunE: getCommand{
apiType: imageRepositoryType,
list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
}.run,
}
func init() {
getImageCmd.AddCommand(getImageRepositoryCmd)
}
func (s imageRepositoryListAdapter) summariseItem(i int, includeNamespace bool) []string {
item := s.Items[i]
status, msg := statusAndMessage(item.Status.Conditions)
var lastScan string
if item.Status.LastScanResult != nil {
lastScan = item.Status.LastScanResult.ScanTime.Time.Format(time.RFC3339)
}
return append(nameColumns(&item, includeNamespace),
status, msg, lastScan, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (s imageRepositoryListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Ready", "Message", "Last scan", "Suspended"}
if includeNamespace {
return append(namespaceHeader, headers...)
}
return headers
}

View File

@@ -0,0 +1,65 @@
/*
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 (
"strconv"
"strings"
"time"
"github.com/spf13/cobra"
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
)
var getImageUpdateCmd = &cobra.Command{
Use: "update",
Short: "Get ImageUpdateAutomation status",
Long: "The get image update command prints the status of ImageUpdateAutomation objects.",
Example: ` # List all image update automation object and their status
flux get image update
# List image update automations from all namespaces
flux get image update --all-namespaces
`,
RunE: getCommand{
apiType: imageUpdateAutomationType,
list: &imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
}.run,
}
func init() {
getImageCmd.AddCommand(getImageUpdateCmd)
}
func (s imageUpdateAutomationListAdapter) summariseItem(i int, includeNamespace bool) []string {
item := s.Items[i]
status, msg := statusAndMessage(item.Status.Conditions)
var lastRun string
if item.Status.LastAutomationRunTime != nil {
lastRun = item.Status.LastAutomationRunTime.Time.Format(time.RFC3339)
}
return append(nameColumns(&item, includeNamespace), status, msg, lastRun, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (s imageUpdateAutomationListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Ready", "Message", "Last run", "Suspended"}
if includeNamespace {
return append(namespaceHeader, headers...)
}
return headers
}

View File

@@ -17,88 +17,43 @@ 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"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
) )
var getKsCmd = &cobra.Command{ var getKsCmd = &cobra.Command{
Use: "kustomizations", Use: "kustomizations",
Aliases: []string{"ks"}, Aliases: []string{"ks", "kustomization"},
Short: "Get Kustomization statuses", Short: "Get Kustomization statuses",
Long: "The get kustomizations command prints the statuses of the resources.", Long: "The get kustomizations command prints the statuses of the resources.",
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", "Revision", "Suspended", "Ready", "Message"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, kustomization := range list.Items {
row := []string{}
if c := meta.GetCondition(kustomization.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
kustomization.GetName(),
kustomization.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(kustomization.Spec.Suspend)),
string(c.Status),
c.Message,
}
} else {
row = []string{
kustomization.GetName(),
kustomization.Status.LastAppliedRevision,
strings.Title(strconv.FormatBool(kustomization.Spec.Suspend)),
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{kustomization.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
} }

View File

@@ -23,7 +23,8 @@ import (
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
@@ -32,9 +33,10 @@ import (
) )
var getReceiverCmd = &cobra.Command{ var getReceiverCmd = &cobra.Command{
Use: "receivers", Use: "receivers",
Short: "Get Receiver statuses", Aliases: []string{"receiver"},
Long: "The get receiver command prints the statuses of the resources.", Short: "Get Receiver statuses",
Long: "The get receiver command prints the statuses of the resources.",
Example: ` # List all Receiver and their status Example: ` # List all Receiver and their status
flux get receivers flux get receivers
`, `,
@@ -46,17 +48,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...)
@@ -65,30 +67,30 @@ 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", "Suspended", "Ready", "Message"} 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
for _, receiver := range list.Items { for _, receiver := range list.Items {
row := []string{} row := []string{}
if c := meta.GetCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil { if c := apimeta.FindStatusCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{ row = []string{
receiver.GetName(), receiver.GetName(),
strings.Title(strconv.FormatBool(receiver.Spec.Suspend)),
string(c.Status), string(c.Status),
c.Message, c.Message,
strings.Title(strconv.FormatBool(receiver.Spec.Suspend)),
} }
} else { } else {
row = []string{ row = []string{
receiver.GetName(), receiver.GetName(),
strings.Title(strconv.FormatBool(receiver.Spec.Suspend)), string(metav1.ConditionFalse),
string(corev1.ConditionFalse),
"waiting to be reconciled", "waiting to be reconciled",
strings.Title(strconv.FormatBool(receiver.Spec.Suspend)),
} }
} }
rows = append(rows, row) rows = append(rows, row)

View File

@@ -21,9 +21,10 @@ import (
) )
var getSourceCmd = &cobra.Command{ var getSourceCmd = &cobra.Command{
Use: "sources", Use: "sources",
Short: "Get source statuses", Aliases: []string{"source"},
Long: "The get source sub-commands print the statuses of the sources.", Short: "Get source statuses",
Long: "The get source sub-commands print the statuses of the sources.",
} }
func init() { func init() {

View File

@@ -17,16 +17,11 @@ limitations under the License.
package main package main
import ( import (
"context"
"os"
"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"
"strconv"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
) )
var getSourceBucketCmd = &cobra.Command{ var getSourceBucketCmd = &cobra.Command{
@@ -35,69 +30,35 @@ var getSourceBucketCmd = &cobra.Command{
Long: "The get sources bucket command prints the status of the Bucket sources.", Long: "The get sources bucket command prints the status of the Bucket sources.",
Example: ` # List all Buckets and their status Example: ` # List all Buckets and their status
flux get sources bucket flux get sources bucket
# List buckets from 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", "Revision", "Ready", "Message"}
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 := meta.GetCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
source.GetName(),
revision,
string(c.Status),
c.Message,
}
} else {
row = []string{
source.GetName(),
revision,
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{source.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
} }

View File

@@ -0,0 +1,64 @@
/*
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 (
"strconv"
"strings"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/spf13/cobra"
)
var getSourceHelmChartCmd = &cobra.Command{
Use: "chart",
Short: "Get HelmChart statuses",
Long: "The get sources chart command prints the status of the HelmCharts.",
Example: ` # List all Helm charts and their status
flux get sources chart
# List Helm charts from all namespaces
flux get sources chart --all-namespaces
`,
RunE: getCommand{
apiType: helmChartType,
list: &helmChartListAdapter{&sourcev1.HelmChartList{}},
}.run,
}
func init() {
getSourceCmd.AddCommand(getSourceHelmChartCmd)
}
func (a *helmChartListAdapter) summariseItem(i int, includeNamespace bool) []string {
item := a.Items[i]
var revision string
if item.GetArtifact() != nil {
revision = item.GetArtifact().Revision
}
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace),
status, msg, revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
}
func (a helmChartListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Ready", "Message", "Revision", "Suspended"}
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}
return headers
}

View File

@@ -17,16 +17,11 @@ limitations under the License.
package main package main
import ( import (
"context" "strconv"
"os" "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"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
) )
var getSourceGitCmd = &cobra.Command{ var getSourceGitCmd = &cobra.Command{
@@ -35,69 +30,35 @@ var getSourceGitCmd = &cobra.Command{
Long: "The get sources git command prints the status of the GitRepository sources.", Long: "The get sources git command prints the status of the GitRepository sources.",
Example: ` # List all Git repositories and their status Example: ` # List all Git repositories and their status
flux get sources git flux get sources git
# List Git repositories from 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", "Revision", "Ready", "Message"}
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 := meta.GetCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
source.GetName(),
revision,
string(c.Status),
c.Message,
}
} else {
row = []string{
source.GetName(),
revision,
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
if allNamespaces {
row = append([]string{source.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
} }

View File

@@ -17,16 +17,11 @@ limitations under the License.
package main package main
import ( import (
"context" "strconv"
"os" "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"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
) )
var getSourceHelmCmd = &cobra.Command{ var getSourceHelmCmd = &cobra.Command{
@@ -35,69 +30,35 @@ var getSourceHelmCmd = &cobra.Command{
Long: "The get sources helm command prints the status of the HelmRepository sources.", Long: "The get sources helm command prints the status of the HelmRepository sources.",
Example: ` # List all Helm repositories and their status Example: ` # List all Helm repositories and their status
flux get sources helm flux get sources helm
# List Helm repositories from 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", "Revision", "Ready", "Message"}
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 := meta.GetCondition(source.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
source.GetName(),
revision,
string(c.Status),
c.Message,
}
} else {
row = []string{
source.GetName(),
revision,
string(corev1.ConditionFalse),
"waiting to be reconciled",
}
}
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)
}

115
cmd/flux/image.go Normal file
View File

@@ -0,0 +1,115 @@
/*
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 (
"sigs.k8s.io/controller-runtime/pkg/client"
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
// 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.
// imagev1.ImageRepository
var imageRepositoryType = apiType{
kind: imagev1.ImageRepositoryKind,
humanKind: "image repository",
}
type imageRepositoryAdapter struct {
*imagev1.ImageRepository
}
func (a imageRepositoryAdapter) asClientObject() client.Object {
return a.ImageRepository
}
// imagev1.ImageRepositoryList
type imageRepositoryListAdapter struct {
*imagev1.ImageRepositoryList
}
func (a imageRepositoryListAdapter) asClientList() client.ObjectList {
return a.ImageRepositoryList
}
func (a imageRepositoryListAdapter) len() int {
return len(a.ImageRepositoryList.Items)
}
// imagev1.ImagePolicy
var imagePolicyType = apiType{
kind: imagev1.ImagePolicyKind,
humanKind: "image policy",
}
type imagePolicyAdapter struct {
*imagev1.ImagePolicy
}
func (a imagePolicyAdapter) asClientObject() client.Object {
return a.ImagePolicy
}
// imagev1.ImagePolicyList
type imagePolicyListAdapter struct {
*imagev1.ImagePolicyList
}
func (a imagePolicyListAdapter) asClientList() client.ObjectList {
return a.ImagePolicyList
}
func (a imagePolicyListAdapter) len() int {
return len(a.ImagePolicyList.Items)
}
// autov1.ImageUpdateAutomation
var imageUpdateAutomationType = apiType{
kind: autov1.ImageUpdateAutomationKind,
humanKind: "image update automation",
}
type imageUpdateAutomationAdapter struct {
*autov1.ImageUpdateAutomation
}
func (a imageUpdateAutomationAdapter) asClientObject() client.Object {
return a.ImageUpdateAutomation
}
// autov1.ImageUpdateAutomationList
type imageUpdateAutomationListAdapter struct {
*autov1.ImageUpdateAutomationList
}
func (a imageUpdateAutomationListAdapter) asClientList() client.ObjectList {
return a.ImageUpdateAutomationList
}
func (a imageUpdateAutomationListAdapter) len() int {
return len(a.ImageUpdateAutomationList.Items)
}

View File

@@ -23,6 +23,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -56,13 +57,15 @@ var (
installDryRun bool installDryRun bool
installManifestsPath string installManifestsPath string
installVersion string installVersion string
installComponents []string installDefaultComponents []string
installExtraComponents []string
installRegistry string installRegistry string
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
) )
func init() { func init() {
@@ -70,30 +73,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(&installComponents, "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,
"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", 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
} }
@@ -103,20 +110,26 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
logger.Generatef("generating manifests") logger.Generatef("generating manifests")
} }
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: installComponents, 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,
} }
if installManifestsPath == "" { if installManifestsPath == "" {
@@ -132,48 +145,46 @@ 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("---")
fmt.Println("# GitOps Toolkit revision", installVersion) fmt.Println("# GitOps Toolkit revision", installVersion)
fmt.Println("# Components:", strings.Join(installComponents, ",")) fmt.Println("# Components:", strings.Join(components, ","))
fmt.Print(manifest.Content) fmt.Print(manifest.Content)
fmt.Println("---") fmt.Println("---")
return nil return nil
} }
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
} }
kubectlArgs := []string{"apply", "-f", filepath.Join(tmpDir, manifest.Path)} kubectlArgs := []string{"apply", "-f", filepath.Join(tmpDir, manifest.Path)}
if installDryRun { if installDryRun {
args = append(args, "--dry-run=client") kubectlArgs = append(kubectlArgs, "--dry-run=client")
applyOutput = utils.ModeOS applyOutput = utils.ModeOS
} }
if _, err := utils.ExecKubectlCommand(ctx, applyOutput, 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")
} }
if installDryRun { if installDryRun {
logger.Successf("install dry-run finished") logger.Successf("install dry-run finished")
return nil return nil
} else { }
logger.Successf("install completed")
statusChecker, err := NewStatusChecker(time.Second, time.Minute)
if err != nil {
return fmt.Errorf("install failed: %w", err)
} }
logger.Waitingf("verifying installation") logger.Waitingf("verifying installation")
for _, deployment := range installComponents { if err := statusChecker.Assess(components...); err != nil {
kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()} return fmt.Errorf("install failed")
if _, err := utils.ExecKubectlCommand(ctx, applyOutput, kubectlArgs...); err != nil {
return fmt.Errorf("install failed")
} else {
logger.Successf("%s ready", deployment)
}
} }
logger.Successf("install finished") logger.Successf("install finished")

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

@@ -16,26 +16,31 @@ limitations under the License.
package main package main
import "fmt" import (
"fmt"
"io"
)
type printLogger struct{} type stderrLogger struct {
stderr io.Writer
func (l printLogger) Actionf(format string, a ...interface{}) {
fmt.Println(``, fmt.Sprintf(format, a...))
} }
func (l printLogger) Generatef(format string, a ...interface{}) { func (l stderrLogger) Actionf(format string, a ...interface{}) {
fmt.Println(``, fmt.Sprintf(format, a...)) fmt.Fprintln(l.stderr, ``, fmt.Sprintf(format, a...))
} }
func (l printLogger) Waitingf(format string, a ...interface{}) { func (l stderrLogger) Generatef(format string, a ...interface{}) {
fmt.Println(``, fmt.Sprintf(format, a...)) fmt.Fprintln(l.stderr, ``, fmt.Sprintf(format, a...))
} }
func (l printLogger) Successf(format string, a ...interface{}) { func (l stderrLogger) Waitingf(format string, a ...interface{}) {
fmt.Println(``, fmt.Sprintf(format, a...)) fmt.Fprintln(l.stderr, ``, fmt.Sprintf(format, a...))
} }
func (l printLogger) Failuref(format string, a ...interface{}) { func (l stderrLogger) Successf(format string, a ...interface{}) {
fmt.Println(``, fmt.Sprintf(format, a...)) fmt.Fprintln(l.stderr, ``, fmt.Sprintf(format, a...))
}
func (l stderrLogger) Failuref(format string, a ...interface{}) {
fmt.Fprintln(l.stderr, ``, fmt.Sprintf(format, a...))
} }

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 = printLogger{} 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")

72
cmd/flux/object.go Normal file
View File

@@ -0,0 +1,72 @@
/*
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 (
"sigs.k8s.io/controller-runtime/pkg/client"
)
// Most commands need one or both of the kind (e.g.,
// `"ImageRepository"`) and a human-palatable name for the kind (e.g.,
// `"image repository"`), to be interpolated into output. It's
// convenient to package these up ahead of time, then the command
// implementation can pick whichever it wants to use.
type apiType struct {
kind, humanKind string
}
// adapter is an interface for a wrapper or alias from which we can
// get a controller-runtime deserialisable value. This is used so that
// you can wrap an API type to give it other useful methods, but still
// use values of the wrapper with `client.Client`, which only deals
// with types that have been added to the schema.
type adapter interface {
asClientObject() client.Object
}
// listAdapater is the analogue to adapter, but for lists; the
// controller runtime distinguishes between methods dealing with
// objects and lists.
type listAdapter interface {
asClientList() client.ObjectList
len() int
}
// universalAdapter is an adapter for any client.Object. Use this if
// there are no other methods needed.
type universalAdapter struct {
obj client.Object
}
func (c universalAdapter) asClientObject() client.Object {
return c.obj
}
// named is for adapters that have Name and Namespace fields, which
// are sometimes handy to get hold of. ObjectMeta implements these, so
// they shouldn't need any extra work.
type named interface {
GetName() string
GetNamespace() string
SetName(string)
SetNamespace(string)
}
func copyName(target, source named) {
target.SetName(source.GetName())
target.SetNamespace(source.GetNamespace())
}

View File

@@ -17,7 +17,20 @@ limitations under the License.
package main package main
import ( import (
"context"
"fmt"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra" "github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils"
) )
var reconcileCmd = &cobra.Command{ var reconcileCmd = &cobra.Command{
@@ -29,3 +42,101 @@ var reconcileCmd = &cobra.Command{
func init() { func init() {
rootCmd.AddCommand(reconcileCmd) rootCmd.AddCommand(reconcileCmd)
} }
type reconcileCommand struct {
apiType
object reconcilable
}
type reconcilable interface {
adapter // to be able to load from the cluster
suspendable // to tell if it's suspended
// these are implemented by anything embedding metav1.ObjectMeta
GetAnnotations() map[string]string
SetAnnotations(map[string]string)
// this is usually implemented by GOTK types, since it's used for meta.SetResourceCondition
GetStatusConditions() *[]metav1.Condition
lastHandledReconcileRequest() string // what was the last handled reconcile request?
successMessage() string // what do you want to tell people when successfully reconciled?
}
func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("%s name is required", reconcile.kind)
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: rootArgs.namespace,
Name: name,
}
err = kubeClient.Get(ctx, namespacedName, reconcile.object.asClientObject())
if err != nil {
return err
}
if reconcile.object.isSuspended() {
return fmt.Errorf("resource is suspended")
}
logger.Actionf("annotating %s %s in %s namespace", reconcile.kind, name, rootArgs.namespace)
if err := requestReconciliation(ctx, kubeClient, namespacedName, reconcile.object); err != nil {
return err
}
logger.Successf("%s annotated", reconcile.kind)
lastHandledReconcileAt := reconcile.object.lastHandledReconcileRequest()
logger.Waitingf("waiting for %s reconciliation", reconcile.kind)
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.object, lastHandledReconcileAt)); err != nil {
return err
}
logger.Successf("%s reconciliation completed", reconcile.kind)
if apimeta.IsStatusConditionFalse(*reconcile.object.GetStatusConditions(), meta.ReadyCondition) {
return fmt.Errorf("%s reconciliation failed", reconcile.kind)
}
logger.Successf(reconcile.object.successMessage())
return nil
}
func reconciliationHandled(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, obj.asClientObject())
if err != nil {
return false, err
}
return obj.lastHandledReconcileRequest() != lastHandledReconcileAt, nil
}
}
func requestReconciliation(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, obj reconcilable) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
if err := kubeClient.Get(ctx, namespacedName, obj.asClientObject()); err != nil {
return err
}
if ann := obj.GetAnnotations(); ann == nil {
obj.SetAnnotations(map[string]string{
meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
})
} else {
ann[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
obj.SetAnnotations(ann)
}
return kubeClient.Update(ctx, obj.asClientObject())
})
}

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